@reclaimprotocol/agent 0.1.4 → 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.
- package/README.md +5 -5
- package/lib/authoring/backend.d.ts +71 -0
- package/lib/authoring/backend.js +24 -0
- package/lib/authoring/find.d.ts +3 -0
- package/lib/authoring/find.js +54 -0
- package/lib/authoring/index.d.ts +4 -0
- package/lib/authoring/index.js +6 -0
- 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 +16 -0
- package/lib/authoring/tools.js +193 -0
- package/lib/authoring/util.d.ts +27 -0
- package/lib/authoring/util.js +21 -0
- package/lib/cdp/capture-engine.js +1 -1
- package/lib/consts.d.ts +6 -1
- package/lib/consts.js +9 -2
- package/lib/mcp/api/operations.gen.js +46 -18
- package/lib/mcp/server.d.ts +2 -25
- package/lib/mcp/server.js +4 -17
- 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/publish.js +2 -2
- 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 +8 -0
- package/lib/mcp/tools/agent/shared.js +125 -0
- package/lib/old/client.d.ts +35 -0
- package/lib/old/client.js +18 -0
- package/lib/old/login-cdp.d.ts +24 -0
- package/lib/old/login-cdp.js +28 -3
- 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 +52 -2
- 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 +189 -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/proof-core.d.ts +1 -1
- package/lib/proof/proof-core.js +1 -1
- 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 +50 -0
- package/lib/tools/registry.js +49 -0
- package/package.json +9 -1
- package/skill/SKILL.builder.md +55 -32
- package/skill/SKILL.old.md +20 -12
- 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/cdp/capture-engine.ts +1 -1
- package/src/consts.ts +15 -2
- package/src/mcp/api/operations.gen.ts +46 -18
- package/src/mcp/server.ts +9 -39
- 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/publish.ts +2 -2
- 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 +56 -0
- package/src/old/login-cdp.ts +28 -3
- package/src/old/sdk-client.ts +105 -0
- package/src/old/to-register.ts +98 -6
- 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 +244 -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/proof-core.ts +1 -1
- 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
|
@@ -1,120 +1,128 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 '
|
|
7
|
-
import type { CaptureStore } from './capture.ts'
|
|
13
|
+
import { defineTool, type RegisteredTool } from '../../../tools/registry.ts'
|
|
8
14
|
|
|
9
|
-
export
|
|
10
|
-
|
|
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
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
|
|
48
|
-
'
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
67
|
-
|
|
49
|
+
['requestId', 'name'],
|
|
50
|
+
),
|
|
68
51
|
},
|
|
69
|
-
async(
|
|
70
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
86
|
-
if(!
|
|
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
|
-
|
|
91
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
reqWithBody,
|
|
98
|
-
target,
|
|
99
|
-
name,
|
|
100
|
-
hash,
|
|
105
|
+
const { provider, secretRefs, extractions } = draftProvider(
|
|
106
|
+
captured, targets, providerName, defaultHash,
|
|
101
107
|
)
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
const
|
|
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 =
|
|
112
|
+
const real = captured.requestHeaders[ref.name]
|
|
107
113
|
if(real !== undefined) {
|
|
108
|
-
|
|
114
|
+
secrets[ref.name] = real
|
|
109
115
|
}
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
const
|
|
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
|
},
|
|
@@ -129,7 +129,7 @@ export function createVersionFromCaptureTool(
|
|
|
129
129
|
{
|
|
130
130
|
name: 'create_provider_version_from_capture',
|
|
131
131
|
description:
|
|
132
|
-
'Persist drafted ReclaimProvider(s) as a
|
|
132
|
+
'Persist drafted ReclaimProvider(s) as a provider version. ' +
|
|
133
133
|
'Call this after run_proof succeeds, passing the `provider` ' +
|
|
134
134
|
'object(s) from propose_provider VERBATIM. This tool builds the ' +
|
|
135
135
|
'body (the requests[] array, webSettings, etc.) for you — do NOT ' +
|
|
@@ -160,7 +160,7 @@ export function createVersionFromCaptureTool(
|
|
|
160
160
|
properties: {
|
|
161
161
|
providerId: {
|
|
162
162
|
type: 'string',
|
|
163
|
-
description: 'UUID of the
|
|
163
|
+
description: 'UUID of the provider to publish the version on.',
|
|
164
164
|
},
|
|
165
165
|
provider: PROVIDER_SCHEMA,
|
|
166
166
|
providers: {
|
|
@@ -1,53 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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(
|
|
7
|
-
return defineTool<
|
|
11
|
+
export function replayTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
12
|
+
return defineTool<Record<string, unknown>>(
|
|
8
13
|
{
|
|
9
14
|
name: 'replay_request',
|
|
10
15
|
description:
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
28
|
+
['draftId'],
|
|
29
|
+
),
|
|
24
30
|
},
|
|
25
|
-
async(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
|
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
|
|
36
|
-
await
|
|
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
|
),
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the shared authoring tools for the MCP, bound to a per-capture LOCAL
|
|
3
|
+
* backend: a Chrome tab over CDP + the in-process attestor SDK. The tool logic
|
|
4
|
+
* (navigate/inspect/propose/replay/prove) is authored once in `../../../
|
|
5
|
+
* authoring` with the shared `defineTool` primitive; this only supplies the
|
|
6
|
+
* MCP host — resolve the backend from `captureId` (the session handle from
|
|
7
|
+
* `start_capture`, since one capture bundles the tab, its requests, and its
|
|
8
|
+
* drafts) and the rich, credential-aware `run_proof` schema.
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
type AuthoringBackend,
|
|
12
|
+
authoringTools,
|
|
13
|
+
extendTool,
|
|
14
|
+
} from '../../../authoring/index.ts'
|
|
15
|
+
import { runAttestorProof } from '../../../proof/attestor.ts'
|
|
16
|
+
import { replayProvider } from '../../../proof/replay.ts'
|
|
17
|
+
import type { RegisteredTool } from '../../server.ts'
|
|
18
|
+
import type { CaptureEntry, CaptureStore } from './capture.ts'
|
|
19
|
+
import { shapeRichProofResult } from './proof.ts'
|
|
20
|
+
|
|
21
|
+
/** The local backend for one capture: drive its tab over CDP, read its
|
|
22
|
+
* captured requests, prove via the in-process attestor, replay by re-fetch. */
|
|
23
|
+
function localBackend(entry: CaptureEntry): AuthoringBackend {
|
|
24
|
+
const { tabClient, session, drafts } = entry
|
|
25
|
+
// The per-tab CDP client is strongly typed to `keyof Commands`; the shared
|
|
26
|
+
// tools drive it with dynamic method/event names, so view it as string-keyed.
|
|
27
|
+
const client = tabClient as unknown as {
|
|
28
|
+
send: (method: string, params: object) => Promise<unknown>
|
|
29
|
+
once: (e: string, cb: () => void) => void
|
|
30
|
+
removeListener: (e: string, cb: () => void) => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
capture: session,
|
|
35
|
+
drafts,
|
|
36
|
+
sendCdp: (method, params) => client.send(method, params ?? {}),
|
|
37
|
+
waitForEvent: (method, timeoutMs) => new Promise((resolve) => {
|
|
38
|
+
const onEvent = () => {
|
|
39
|
+
cleanup()
|
|
40
|
+
resolve('fired')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const timer = setTimeout(() => {
|
|
44
|
+
cleanup()
|
|
45
|
+
resolve('timeout')
|
|
46
|
+
}, timeoutMs)
|
|
47
|
+
const cleanup = () => {
|
|
48
|
+
clearTimeout(timer)
|
|
49
|
+
client.removeListener(method, onEvent)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
client.once(method, onEvent)
|
|
53
|
+
}),
|
|
54
|
+
replay: async(draft, opts) => {
|
|
55
|
+
// Auth-bound check: re-fetch with the secrets dropped. replayProvider
|
|
56
|
+
// already does a live Node fetch, so empty secrets == anonymous.
|
|
57
|
+
const secrets = opts?.withoutSecrets ? {} : draft.secrets
|
|
58
|
+
const result = await replayProvider(draft.provider, secrets)
|
|
59
|
+
return {
|
|
60
|
+
status: result.status,
|
|
61
|
+
matchesOriginal: result.matchesOriginal,
|
|
62
|
+
bodySnippet: result.body.slice(0, 256),
|
|
63
|
+
...(result.hints !== undefined ? { hints: result.hints } : {}),
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
prove: async(draft, opts) => {
|
|
67
|
+
const result = await runAttestorProof(
|
|
68
|
+
draft.provider,
|
|
69
|
+
draft.secrets,
|
|
70
|
+
{
|
|
71
|
+
...(opts?.ownerAddress ? { ownerAddress: opts.ownerAddress } : {}),
|
|
72
|
+
...(opts?.attestorUrl ? { attestorUrl: opts.attestorUrl } : {}),
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
return shapeRichProofResult(result, {
|
|
76
|
+
provider: draft.provider,
|
|
77
|
+
hasSecrets: Object.keys(draft.secrets).length > 0,
|
|
78
|
+
...(opts?.attestorUrl ? { attestorUrl: opts.attestorUrl } : {}),
|
|
79
|
+
})
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The MCP's rich, credential-aware `run_proof` schema, merged onto the shared
|
|
85
|
+
* tool by the host below (the cloud's TEE signs in-enclave, so it needs none
|
|
86
|
+
* of this). */
|
|
87
|
+
const RUN_PROOF_EXTRAS = {
|
|
88
|
+
description:
|
|
89
|
+
'Run a full zkTLS proof of the drafted recipe through the Reclaim '
|
|
90
|
+
+ 'attestor SDK. Use only after replay_request looks right — attestor '
|
|
91
|
+
+ 'calls are expensive. Writes the full claim to '
|
|
92
|
+
+ '~/.reclaim/claims/<timestamp>.json and returns identifier + '
|
|
93
|
+
+ 'extractedParameters + claimPath + a prettified `claim` (request '
|
|
94
|
+
+ 'dropped, byte arrays as base64). With RECLAIM_AGENT_TRACE=1, also '
|
|
95
|
+
+ 'writes a per-call diagnostic dump (input provider, sentParams, full '
|
|
96
|
+
+ 'attestor log stream, result) and returns it as `runLogPath`. '
|
|
97
|
+
+ '`attestorUrl` overrides the default only when the dev supplies a '
|
|
98
|
+
+ 'specific alternative — do NOT invent backup URLs. `ownerAddress` '
|
|
99
|
+
+ '(the credential to sign with) is REQUIRED — call resolve_owner_key '
|
|
100
|
+
+ 'first. errorKind="missing-owner-key" means that address has no known '
|
|
101
|
+
+ 'private key; re-run resolve_owner_key or issue/import a credential.',
|
|
102
|
+
properties: {
|
|
103
|
+
ownerAddress: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
pattern: '^0x[0-9a-fA-F]{40}$',
|
|
106
|
+
description:
|
|
107
|
+
'ETH address of the credential to sign the claim with. Get it '
|
|
108
|
+
+ 'from resolve_owner_key first. The private key is resolved '
|
|
109
|
+
+ 'locally (cache / env / .env) — never passed here.',
|
|
110
|
+
},
|
|
111
|
+
attestorUrl: { type: 'string' },
|
|
112
|
+
},
|
|
113
|
+
required: ['ownerAddress'],
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The shared authoring tools mapped to the MCP's shape: resolve each call's
|
|
117
|
+
* local backend from `args.captureId`, and widen the input schema — every tool
|
|
118
|
+
* takes `captureId`; `run_proof` also gets the credential-aware contract. The
|
|
119
|
+
* tools are authored plain in `../../../authoring`; this only wraps them with
|
|
120
|
+
* `extendTool` (handlers untouched — loose arg parse keeps `captureId`). */
|
|
121
|
+
export function sharedAuthoringTools(captures: CaptureStore): RegisteredTool[] {
|
|
122
|
+
const resolveBackend = (args: Record<string, unknown>): AuthoringBackend => {
|
|
123
|
+
const captureId = String(args.captureId)
|
|
124
|
+
const entry = captures.sessions.get(captureId)
|
|
125
|
+
if(!entry) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`no capture ${captureId} — call start_capture first `
|
|
128
|
+
+ '(it has been reset or never started).',
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return localBackend(entry)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return authoringTools(resolveBackend).map((tool) => {
|
|
136
|
+
if(tool.definition.name === 'run_proof') {
|
|
137
|
+
return extendTool(tool, {
|
|
138
|
+
description: RUN_PROOF_EXTRAS.description,
|
|
139
|
+
properties: {
|
|
140
|
+
captureId: { type: 'string' },
|
|
141
|
+
...RUN_PROOF_EXTRAS.properties,
|
|
142
|
+
},
|
|
143
|
+
required: ['captureId', ...RUN_PROOF_EXTRAS.required],
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return extendTool(tool, {
|
|
148
|
+
properties: { captureId: { type: 'string' } },
|
|
149
|
+
required: ['captureId'],
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
}
|
package/src/old/client.ts
CHANGED
|
@@ -34,6 +34,15 @@ export interface RegisterProviderBody {
|
|
|
34
34
|
useProxy?: boolean
|
|
35
35
|
providerType?: 'PRIVATE' | 'PUBLIC'
|
|
36
36
|
requestData: unknown[]
|
|
37
|
+
/** Page-injection script (old-devtools name for the builder's
|
|
38
|
+
* `webSettings.jsUserScripts`). Top-level on register; nested under
|
|
39
|
+
* `providerConfig` on the config (add-version) body instead. */
|
|
40
|
+
customInjection?: string
|
|
41
|
+
/** Accepted at register too (unlike most `providerConfig` extras, which
|
|
42
|
+
* the register controller only reads on config/add-version). */
|
|
43
|
+
userAgent?: { ios?: string, android?: string }
|
|
44
|
+
/** Accepted at register too. */
|
|
45
|
+
pageTitle?: string
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
export interface UpdateProviderConfigBody {
|
|
@@ -42,6 +51,19 @@ export interface UpdateProviderConfigBody {
|
|
|
42
51
|
versionInfo: string
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
/** Body for `POST /api/providers/:providerId` (`updateProviderMetadata`) —
|
|
55
|
+
* a genuine PARTIAL update on the PROVIDER document itself (name/
|
|
56
|
+
* description/providerType/tags/isActive), distinct from both `register`
|
|
57
|
+
* (creates the provider) and `config` (adds an immutable new version). Only
|
|
58
|
+
* the fields present get changed; everything else is left untouched. */
|
|
59
|
+
export interface UpdateProviderMetadataBody {
|
|
60
|
+
name?: string
|
|
61
|
+
description?: string
|
|
62
|
+
providerType?: 'PRIVATE' | 'PUBLIC'
|
|
63
|
+
tags?: string[]
|
|
64
|
+
isActive?: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
export interface MyProvidersQuery {
|
|
46
68
|
pageKey?: number
|
|
47
69
|
pageSize?: number
|
|
@@ -121,6 +143,40 @@ export class ReclaimOldClient {
|
|
|
121
143
|
)
|
|
122
144
|
}
|
|
123
145
|
|
|
146
|
+
/** Full `providerConfig` (loginUrl, customInjection, requestData, ...) for
|
|
147
|
+
* one version — the latest when `version` is omitted. Readable for ANY
|
|
148
|
+
* non-archived provider (not owner-restricted), same as the dashboard's
|
|
149
|
+
* edit-form prefill fetch. */
|
|
150
|
+
getProviderConfig(
|
|
151
|
+
providerId: string,
|
|
152
|
+
version?: string,
|
|
153
|
+
): Promise<unknown> {
|
|
154
|
+
return this.#request(
|
|
155
|
+
'GET',
|
|
156
|
+
`/providers/${encodeURIComponent(providerId)}/config`,
|
|
157
|
+
{
|
|
158
|
+
auth: true,
|
|
159
|
+
query: version ? { versionNumber: version } : undefined,
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Partial update on the PROVIDER document itself — name/description/
|
|
165
|
+
* providerType/tags/isActive, after creation. The backend 403s if you
|
|
166
|
+
* don't own the provider (and aren't admin); other providerConfig-style
|
|
167
|
+
* fields sent here are simply ignored (this endpoint only reads the
|
|
168
|
+
* ones above from the body). */
|
|
169
|
+
updateProviderMetadata(
|
|
170
|
+
providerId: string,
|
|
171
|
+
body: UpdateProviderMetadataBody,
|
|
172
|
+
): Promise<unknown> {
|
|
173
|
+
return this.#request(
|
|
174
|
+
'POST',
|
|
175
|
+
`/providers/${encodeURIComponent(providerId)}`,
|
|
176
|
+
{ body, auth: true },
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
124
180
|
/** Analytics logs for a verification session, from the logs service (a
|
|
125
181
|
* DIFFERENT host than the devtools API — hence its own fetch, not
|
|
126
182
|
* `#request`). Attaches the caller identity when one is set; queried
|
package/src/old/login-cdp.ts
CHANGED
|
@@ -18,16 +18,39 @@ import { DEFAULT_LOGIN_URL, oldLoginUrl } from '../consts.ts'
|
|
|
18
18
|
* `firebaseLocalStorage`) in case a build switches persistence. Returns the
|
|
19
19
|
* token string, or null when the user has not finished logging in yet.
|
|
20
20
|
*
|
|
21
|
+
* The CDP-launched Chrome reuses a PERSISTENT profile dir across runs, so a
|
|
22
|
+
* stale `firebase:authUser:*` entry from an earlier (now-expired, ~1h TTL)
|
|
23
|
+
* session is often still sitting in storage the instant the page loads —
|
|
24
|
+
* before the developer has clicked anything. Returning that immediately
|
|
25
|
+
* makes the poll "succeed" on tick one, closing the tab before the developer
|
|
26
|
+
* can sign in, and the captured token then fails backend verification. So
|
|
27
|
+
* every candidate's JWT `exp` claim is checked here (in-page, where `atob` is
|
|
28
|
+
* available) and stale ones are skipped — the loop keeps polling until a
|
|
29
|
+
* genuinely fresh token shows up from an interactive sign-in.
|
|
30
|
+
*
|
|
21
31
|
* Evaluated in-page with `awaitPromise` so the async IndexedDB branch resolves.
|
|
22
32
|
*/
|
|
23
|
-
const EXTRACT_TOKEN_JS = `(async () => {
|
|
33
|
+
export const EXTRACT_TOKEN_JS = `(async () => {
|
|
34
|
+
const isFresh = (token) => {
|
|
35
|
+
try {
|
|
36
|
+
const payload = JSON.parse(
|
|
37
|
+
atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))
|
|
38
|
+
)
|
|
39
|
+
// No exp claim to check — don't block a token we can't evaluate.
|
|
40
|
+
if (typeof payload.exp !== 'number') return true
|
|
41
|
+
// 60s buffer so a token that's about to expire isn't treated as fresh.
|
|
42
|
+
return payload.exp * 1000 > Date.now() + 60000
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
}
|
|
24
47
|
try {
|
|
25
48
|
for (let i = 0; i < localStorage.length; i++) {
|
|
26
49
|
const k = localStorage.key(i)
|
|
27
50
|
if (k && k.indexOf('firebase:authUser:') === 0) {
|
|
28
51
|
const v = JSON.parse(localStorage.getItem(k) || 'null')
|
|
29
52
|
const t = v && v.stsTokenManager && v.stsTokenManager.accessToken
|
|
30
|
-
if (t) return t
|
|
53
|
+
if (t && isFresh(t)) return t
|
|
31
54
|
}
|
|
32
55
|
}
|
|
33
56
|
} catch (e) {}
|
|
@@ -49,7 +72,9 @@ const EXTRACT_TOKEN_JS = `(async () => {
|
|
|
49
72
|
all.onsuccess = () => {
|
|
50
73
|
for (const row of (all.result || [])) {
|
|
51
74
|
const m = row && row.value && row.value.stsTokenManager
|
|
52
|
-
if (m && m.accessToken
|
|
75
|
+
if (m && m.accessToken && isFresh(m.accessToken)) {
|
|
76
|
+
resolve(m.accessToken); return
|
|
77
|
+
}
|
|
53
78
|
}
|
|
54
79
|
resolve(null)
|
|
55
80
|
}
|