@reclaimprotocol/agent 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cdp/launcher.js +4 -3
- package/lib/consts.d.ts +68 -0
- package/lib/consts.js +134 -0
- package/lib/logger.js +2 -1
- package/lib/mcp/api/operations.gen.d.ts +11 -7
- package/lib/mcp/api/operations.gen.js +356 -199
- package/lib/mcp/server.d.ts +0 -6
- package/lib/mcp/server.js +0 -30
- package/lib/mcp/start.js +7 -7
- package/lib/mcp/tools/agent/build.d.ts +5 -9
- package/lib/mcp/tools/agent/build.js +5 -9
- package/lib/mcp/tools/agent/proof.js +2 -1
- package/lib/mcp/tools/agent/publish.js +166 -77
- package/lib/mcp/tools/authenticate/decrypt-key.d.ts +10 -23
- package/lib/mcp/tools/authenticate/decrypt-key.js +23 -26
- package/lib/mcp/tools/authenticate/eth-key.d.ts +0 -7
- package/lib/mcp/tools/authenticate/eth-key.js +5 -19
- package/lib/mcp/tools/authenticate/issue.js +1 -1
- package/lib/mcp/tools/verification/build.d.ts +1 -1
- package/lib/mcp/tools/verification/build.js +1 -1
- package/lib/mcp/tools/verification/result.d.ts +4 -5
- package/lib/mcp/tools/verification/result.js +17 -17
- package/lib/old/flag.d.ts +4 -1
- package/lib/old/flag.js +4 -3
- package/lib/old/identity.d.ts +0 -10
- package/lib/old/identity.js +8 -7
- package/lib/old/login-cdp.d.ts +0 -3
- package/lib/old/login-cdp.js +2 -4
- package/lib/old/to-register.d.ts +5 -3
- package/lib/old/to-register.js +20 -15
- package/lib/old/tools/authenticate.js +2 -1
- package/lib/old/tools/publish.js +97 -13
- package/lib/paths.js +2 -2
- package/lib/proof/attestor.js +2 -1
- package/lib/proof/proof-core.js +13 -2
- package/lib/provider/injection-doc.d.ts +1 -0
- package/lib/provider/injection-doc.js +17 -0
- package/lib/provider/match-redaction-warning.d.ts +25 -0
- package/lib/provider/match-redaction-warning.js +35 -0
- package/lib/provider/schema.d.ts +16 -0
- package/lib/provider/schema.js +11 -1
- package/lib/provider/to-version.d.ts +59 -7
- package/lib/provider/to-version.js +79 -28
- package/package.json +1 -1
- package/skill/builder-mode.md +8 -2
- package/skill/injection-api.md +42 -0
- package/skill/old-mode.md +4 -2
- package/src/cdp/launcher.ts +5 -4
- package/src/consts.ts +164 -0
- package/src/logger.ts +2 -1
- package/src/mcp/api/operations.gen.ts +358 -203
- package/src/mcp/server.ts +0 -46
- package/src/mcp/start.ts +13 -7
- package/src/mcp/tools/agent/build.ts +5 -9
- package/src/mcp/tools/agent/proof.ts +2 -1
- package/src/mcp/tools/agent/publish.ts +212 -94
- package/src/mcp/tools/authenticate/decrypt-key.ts +24 -29
- package/src/mcp/tools/authenticate/eth-key.ts +5 -25
- package/src/mcp/tools/authenticate/issue.ts +1 -1
- package/src/mcp/tools/verification/build.ts +1 -1
- package/src/mcp/tools/verification/result.ts +16 -17
- package/src/old/flag.ts +4 -3
- package/src/old/identity.ts +8 -7
- package/src/old/login-cdp.ts +2 -5
- package/src/old/to-register.ts +22 -16
- package/src/old/tools/authenticate.ts +2 -1
- package/src/old/tools/publish.ts +124 -20
- package/src/paths.ts +2 -2
- package/src/proof/attestor.ts +2 -1
- package/src/proof/proof-core.ts +13 -3
- package/src/provider/injection-doc.ts +21 -0
- package/src/provider/match-redaction-warning.ts +53 -0
- package/src/provider/schema.ts +23 -0
- package/src/provider/to-version.ts +137 -31
package/src/mcp/server.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
2
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
3
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
4
|
-
import type { Tool } from '@modelcontextprotocol/sdk/types.js'
|
|
5
|
-
import {
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema,
|
|
8
|
-
} from '@modelcontextprotocol/sdk/types.js'
|
|
9
2
|
import type { JSONSchema } from 'zod/v4/core'
|
|
10
3
|
import { toZod } from './api/to-zod.ts'
|
|
11
4
|
import { remapErrorAsResponse } from './remap_errors.ts'
|
|
@@ -79,42 +72,3 @@ export function registerToolList(server: McpServer, tools: RegisteredTool[]) {
|
|
|
79
72
|
)
|
|
80
73
|
}
|
|
81
74
|
}
|
|
82
|
-
|
|
83
|
-
export class ReclaimMcpServer {
|
|
84
|
-
#tools = new Map<string, RegisteredTool>()
|
|
85
|
-
#server: Server
|
|
86
|
-
|
|
87
|
-
constructor() {
|
|
88
|
-
this.#server = new Server(
|
|
89
|
-
{ name: 'reclaim-agent', version: '0.1.0' },
|
|
90
|
-
{ capabilities: { tools: {} } },
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
this.#server.setRequestHandler(ListToolsRequestSchema, async() => ({
|
|
94
|
-
// Double-cast: our `ToolDefinition` carries the same fields as the
|
|
95
|
-
// SDK's `Tool` but the SDK's type is strict-narrowed and won't
|
|
96
|
-
// accept ours without going through `unknown`.
|
|
97
|
-
tools: [...this.#tools.values()].map(
|
|
98
|
-
(t) => t.definition as unknown as Tool,
|
|
99
|
-
),
|
|
100
|
-
}))
|
|
101
|
-
|
|
102
|
-
this.#server.setRequestHandler(CallToolRequestSchema, async(req) => {
|
|
103
|
-
const tool = this.#tools.get(req.params.name)
|
|
104
|
-
if(!tool) {
|
|
105
|
-
throw new Error(`unknown tool: ${req.params.name}`)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const result = await tool.handler(req.params.arguments ?? {})
|
|
109
|
-
return { content: [{ type: 'text', text: JSON.stringify(result) }] }
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
register(tool: RegisteredTool) {
|
|
114
|
-
this.#tools.set(tool.definition.name, tool)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async listen(): Promise<void> {
|
|
118
|
-
await this.#server.connect(new StdioServerTransport())
|
|
119
|
-
}
|
|
120
|
-
}
|
package/src/mcp/start.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import { ReclaimClient } from '@reclaimprotocol/client'
|
|
4
|
+
import {
|
|
5
|
+
agentDisabled,
|
|
6
|
+
apiToken,
|
|
7
|
+
apiUrl,
|
|
8
|
+
oldApiUrl,
|
|
9
|
+
useOldDevtools,
|
|
10
|
+
} from '../consts.ts'
|
|
11
|
+
import { ReclaimOldClient } from '../old/client.ts'
|
|
6
12
|
import { resolveInitialIdentity } from '../old/identity.ts'
|
|
7
13
|
import { OLD_MODE_INSTRUCTIONS } from '../old/instructions.ts'
|
|
8
14
|
import { buildOldBackendTools } from '../old/tools/build.ts'
|
|
@@ -34,7 +40,7 @@ export async function startMcpServer() {
|
|
|
34
40
|
// backend-independent and registered in both modes. Skipped in hosted
|
|
35
41
|
// deployments — set RECLAIM_AGENT_DISABLED=1 to register only the API
|
|
36
42
|
// surface.
|
|
37
|
-
const agentToolsEnabled =
|
|
43
|
+
const agentToolsEnabled = !agentDisabled()
|
|
38
44
|
|
|
39
45
|
if(oldMode) {
|
|
40
46
|
// OLD-devtools mode (the default): the legacy backend is still in
|
|
@@ -42,7 +48,7 @@ export async function startMcpServer() {
|
|
|
42
48
|
// are disabled; a small ReclaimOldClient serves authenticate / publish /
|
|
43
49
|
// get_me_providers instead.
|
|
44
50
|
const oldClient = new ReclaimOldClient({
|
|
45
|
-
baseUrl:
|
|
51
|
+
baseUrl: oldApiUrl(),
|
|
46
52
|
identity: resolveInitialIdentity(),
|
|
47
53
|
})
|
|
48
54
|
|
|
@@ -55,9 +61,9 @@ export async function startMcpServer() {
|
|
|
55
61
|
// NOTE: registerTools() is intentionally NOT called — generated tools off.
|
|
56
62
|
} else {
|
|
57
63
|
const client = new ReclaimClient({
|
|
58
|
-
baseUrl:
|
|
64
|
+
baseUrl: apiUrl(),
|
|
59
65
|
// Priority: explicit env var → cached session token → unauthenticated
|
|
60
|
-
token:
|
|
66
|
+
token: apiToken() || readSession()?.token,
|
|
61
67
|
headers: { 'User-Agent': `${NAME}/${VERSION}` },
|
|
62
68
|
})
|
|
63
69
|
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Factory entry-point for embedding the reclaim-agent's MCP tools into
|
|
3
|
-
* a host MCP server.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the low-level wrapper around `@modelcontextprotocol/sdk`'s Server).
|
|
7
|
-
* - `@reclaimprotocol/client`'s MCP server (uses `McpServer`, the
|
|
8
|
-
* high-level SDK wrapper). Calls `registerAgentTools` to fold the
|
|
9
|
-
* agent's 16 tools alongside its auto-generated API tools.
|
|
3
|
+
* a host MCP server via `registerToolList` (server.ts) on the high-level
|
|
4
|
+
* `@modelcontextprotocol/sdk` `McpServer`. Folds the agent's tools
|
|
5
|
+
* alongside the auto-generated API tools.
|
|
10
6
|
*
|
|
11
7
|
* Shared state — Chrome attach state, capture store, secret store — is
|
|
12
8
|
* built once per server and threaded through every tool factory.
|
|
@@ -45,8 +41,8 @@ function buildAgentState(): AgentState {
|
|
|
45
41
|
}
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
/** Build the flat list of `RegisteredTool` entries
|
|
49
|
-
*
|
|
44
|
+
/** Build the flat list of `RegisteredTool` entries, ready for
|
|
45
|
+
* `registerToolList`.
|
|
50
46
|
* Pass `client` to include the builder `create_provider_version_from_capture`;
|
|
51
47
|
* omit it (e.g. OLD-devtools mode) to register only the backend-independent
|
|
52
48
|
* capture/proof tools — old mode supplies its own publish tool. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
+
import { agentTraceEnabled } from '../../../consts.ts'
|
|
3
4
|
import { reclaimHomeDirPath } from '../../../paths.ts'
|
|
4
5
|
import {
|
|
5
6
|
runAttestorProof,
|
|
@@ -237,7 +238,7 @@ export function proofTool(secrets: SecretStore): RegisteredTool {
|
|
|
237
238
|
// when investigating proof failures and the file at runLogPath
|
|
238
239
|
// will carry the input provider, sentParams, full attestor log
|
|
239
240
|
// stream, and result.
|
|
240
|
-
if(
|
|
241
|
+
if(agentTraceEnabled()) {
|
|
241
242
|
const runLogPath = writeProofRunDump({
|
|
242
243
|
provider,
|
|
243
244
|
hasSecretParamsRef: secretParamsRef !== undefined,
|
|
@@ -1,39 +1,158 @@
|
|
|
1
1
|
import type { ReclaimClient } from '@reclaimprotocol/client'
|
|
2
|
+
import type { JSONSchema } from 'zod/v4/core'
|
|
3
|
+
import { INJECTION_GUIDE } from '../../../provider/injection-doc.ts'
|
|
4
|
+
import {
|
|
5
|
+
countsFor,
|
|
6
|
+
matchRedactionWarnings,
|
|
7
|
+
} from '../../../provider/match-redaction-warning.ts'
|
|
2
8
|
import type { ReclaimProvider } from '../../../provider/schema.ts'
|
|
9
|
+
import type { AllowedJsRequest } from '../../../provider/to-version.ts'
|
|
3
10
|
import {
|
|
4
11
|
nextVersion,
|
|
5
12
|
providerToCreateVersionRequest,
|
|
6
13
|
} from '../../../provider/to-version.ts'
|
|
7
14
|
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
8
15
|
|
|
16
|
+
/** Schemas shared by the top-level `provider`/`providers` and the lighter
|
|
17
|
+
* `allowedJsRequests` drafts — keeps the request shape from drifting between
|
|
18
|
+
* them. */
|
|
19
|
+
const RESPONSE_MATCHES_SCHEMA: JSONSchema.Schema = {
|
|
20
|
+
type: 'array',
|
|
21
|
+
description: 'What the attestor checks on the revealed response slice.',
|
|
22
|
+
items: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
type: { type: 'string', enum: ['regex', 'contains'] },
|
|
26
|
+
value: { type: 'string' },
|
|
27
|
+
},
|
|
28
|
+
required: ['type', 'value'],
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const RESPONSE_REDACTIONS_SCHEMA: JSONSchema.Schema = {
|
|
33
|
+
type: 'array',
|
|
34
|
+
description:
|
|
35
|
+
'Which portions of the response the prover reveals ' +
|
|
36
|
+
'(xPath / jsonPath / regex; hash to OPRF the value). Paired with ' +
|
|
37
|
+
'responseMatches by index — keep them aligned 1-to-1. If you list more ' +
|
|
38
|
+
'redactions than matches, the tool pads matches with no-op optional ' +
|
|
39
|
+
'entries so older index-paired claim creators stay happy.',
|
|
40
|
+
items: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
xPath: { type: 'string' },
|
|
44
|
+
jsonPath: { type: 'string' },
|
|
45
|
+
regex: { type: 'string' },
|
|
46
|
+
hash: { type: 'string', enum: ['oprf', 'oprf-mpc', 'oprf-raw'] },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Properties every drafted request shares (top-level providers and the
|
|
52
|
+
* injected `allowedJsRequests`). */
|
|
53
|
+
const REQUEST_DRAFT_PROPERTIES: Record<string, JSONSchema.Schema> = {
|
|
54
|
+
url: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description:
|
|
57
|
+
'Exact API URL the attestor replays, with {{param}} placeholders ' +
|
|
58
|
+
'for any templated values.',
|
|
59
|
+
},
|
|
60
|
+
method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH'] },
|
|
61
|
+
body: { type: 'string', description: 'Request body template, if any.' },
|
|
62
|
+
responseMatches: RESPONSE_MATCHES_SCHEMA,
|
|
63
|
+
responseRedactions: RESPONSE_REDACTIONS_SCHEMA,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A full provider draft (from propose_provider), passed through verbatim. */
|
|
67
|
+
const PROVIDER_SCHEMA: JSONSchema.Schema = {
|
|
68
|
+
type: 'object',
|
|
69
|
+
description:
|
|
70
|
+
'A `provider` object from propose_provider, passed through verbatim — ' +
|
|
71
|
+
'the tool builds the version body (the requests[] entry etc.) for you.',
|
|
72
|
+
properties: {
|
|
73
|
+
name: { type: 'string', description: 'Verification name.' },
|
|
74
|
+
...REQUEST_DRAFT_PROPERTIES,
|
|
75
|
+
headers: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
description:
|
|
78
|
+
'Request headers from the capture. Secret headers are stripped ' +
|
|
79
|
+
'before persistence; the client re-supplies them at runtime.',
|
|
80
|
+
},
|
|
81
|
+
paramValues: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
description:
|
|
84
|
+
'Real values for the {{param}} placeholders, e.g. ' +
|
|
85
|
+
'{ "username": "Syed" }.',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
required: ['name', 'url', 'method', 'responseMatches'],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** One `allowedJsRequests` template: a request the injection script may fire,
|
|
92
|
+
* plus pagination/optionality flags. */
|
|
93
|
+
const ALLOWED_JS_REQUEST_SCHEMA: JSONSchema.Schema = {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
...REQUEST_DRAFT_PROPERTIES,
|
|
97
|
+
multiple: {
|
|
98
|
+
type: 'boolean',
|
|
99
|
+
description:
|
|
100
|
+
'Allow the template to match many requests (e.g. pagination). ' +
|
|
101
|
+
'Default true.',
|
|
102
|
+
},
|
|
103
|
+
required: {
|
|
104
|
+
type: 'boolean',
|
|
105
|
+
description:
|
|
106
|
+
'Fail validation if no request matches this template. Default true.',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
required: ['url', 'method'],
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface PublishArgs {
|
|
113
|
+
providerId: string
|
|
114
|
+
/** Single-provider back-compat input; normalized to `[provider]`. */
|
|
115
|
+
provider?: ReclaimProvider
|
|
116
|
+
/** Multi-request input — one version carrying every request. */
|
|
117
|
+
providers?: ReclaimProvider[]
|
|
118
|
+
initialUrl: string
|
|
119
|
+
notes?: string
|
|
120
|
+
version?: { major: number, minor: number, patch: number }
|
|
121
|
+
jsUserScripts?: string
|
|
122
|
+
allowedJsRequests?: AllowedJsRequest[]
|
|
123
|
+
}
|
|
124
|
+
|
|
9
125
|
export function createVersionFromCaptureTool(
|
|
10
126
|
client: ReclaimClient,
|
|
11
127
|
): RegisteredTool {
|
|
12
|
-
return defineTool<
|
|
13
|
-
providerId: string
|
|
14
|
-
provider: ReclaimProvider
|
|
15
|
-
initialUrl: string
|
|
16
|
-
notes?: string
|
|
17
|
-
version?: { major: number, minor: number, patch: number }
|
|
18
|
-
}>(
|
|
128
|
+
return defineTool<PublishArgs>(
|
|
19
129
|
{
|
|
20
130
|
name: 'create_provider_version_from_capture',
|
|
21
131
|
description:
|
|
22
|
-
'Persist
|
|
23
|
-
'Call this after run_proof succeeds, passing the `provider`
|
|
24
|
-
'from propose_provider VERBATIM. This tool builds the
|
|
132
|
+
'Persist drafted ReclaimProvider(s) as a verification version. ' +
|
|
133
|
+
'Call this after run_proof succeeds, passing the `provider` ' +
|
|
134
|
+
'object(s) from propose_provider VERBATIM. This tool builds the ' +
|
|
25
135
|
'body (the requests[] array, webSettings, etc.) for you — do NOT ' +
|
|
26
136
|
'hand-construct that shape or call create_provider_version directly. ' +
|
|
137
|
+
'For a MULTI-REQUEST provider, pass `providers: [...]` (each entry a ' +
|
|
138
|
+
'proven draft) — every entry becomes one request in the version, and ' +
|
|
139
|
+
'all are required at verification time. Pass a single `provider` ' +
|
|
140
|
+
'for the one-request case (exactly one of `provider`/`providers`). ' +
|
|
141
|
+
'For data gated behind login/navigation, supply `jsUserScripts` (a ' +
|
|
142
|
+
'page-injection script that drives the page to where the request ' +
|
|
143
|
+
'fires) and/or `allowedJsRequests` (extra requests the script may ' +
|
|
144
|
+
'fire, e.g. pagination). ' +
|
|
27
145
|
'Omit `version` to have the tool auto-select the next available ' +
|
|
28
146
|
'version (it lists existing versions and bumps past the highest). ' +
|
|
29
147
|
'The version lands on the draft branch — to go live, call ' +
|
|
30
148
|
'`publish_provider_version` (PRIVATE) or ' +
|
|
31
149
|
'`submit_provider_version_for_review` (PUBLIC) afterwards. ' +
|
|
32
150
|
'`requiredContext` is derived from any {{context.<name>}} ' +
|
|
33
|
-
'placeholders
|
|
34
|
-
'
|
|
35
|
-
'MUST send when creating
|
|
36
|
-
'values exactly; a missing
|
|
151
|
+
'placeholders across ALL requests (plus injected requests / script) ' +
|
|
152
|
+
'and returned on the created version — when present, tell the dev ' +
|
|
153
|
+
'verbatim which context fields consumers MUST send when creating ' +
|
|
154
|
+
'sessions (verification matches those values exactly; a missing ' +
|
|
155
|
+
'field is a 400 at session create). ' +
|
|
37
156
|
'Refine types/descriptions via `update_provider_version` while ' +
|
|
38
157
|
'still on draft.',
|
|
39
158
|
inputSchema: {
|
|
@@ -43,77 +162,15 @@ export function createVersionFromCaptureTool(
|
|
|
43
162
|
type: 'string',
|
|
44
163
|
description: 'UUID of the verification to publish the version on.',
|
|
45
164
|
},
|
|
46
|
-
provider:
|
|
47
|
-
|
|
165
|
+
provider: PROVIDER_SCHEMA,
|
|
166
|
+
providers: {
|
|
167
|
+
type: 'array',
|
|
48
168
|
description:
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
url: {
|
|
55
|
-
type: 'string',
|
|
56
|
-
description:
|
|
57
|
-
'Exact API URL the attestor replays, with {{param}} ' +
|
|
58
|
-
'placeholders for any templated values.',
|
|
59
|
-
},
|
|
60
|
-
method: {
|
|
61
|
-
type: 'string',
|
|
62
|
-
enum: ['GET', 'POST', 'PUT', 'PATCH'],
|
|
63
|
-
},
|
|
64
|
-
headers: {
|
|
65
|
-
type: 'object',
|
|
66
|
-
description:
|
|
67
|
-
'Request headers from the capture. Secret headers are ' +
|
|
68
|
-
'stripped before persistence; the client re-supplies them ' +
|
|
69
|
-
'at runtime.',
|
|
70
|
-
},
|
|
71
|
-
body: {
|
|
72
|
-
type: 'string',
|
|
73
|
-
description: 'Request body template, if any.',
|
|
74
|
-
},
|
|
75
|
-
responseMatches: {
|
|
76
|
-
type: 'array',
|
|
77
|
-
description:
|
|
78
|
-
'What the attestor checks on the revealed response slice.',
|
|
79
|
-
items: {
|
|
80
|
-
type: 'object',
|
|
81
|
-
properties: {
|
|
82
|
-
type: { type: 'string', enum: ['regex', 'contains'] },
|
|
83
|
-
value: { type: 'string' },
|
|
84
|
-
},
|
|
85
|
-
required: ['type', 'value'],
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
responseRedactions: {
|
|
89
|
-
type: 'array',
|
|
90
|
-
description:
|
|
91
|
-
'Which portions of the response the prover reveals ' +
|
|
92
|
-
'(xPath / jsonPath / regex; hash to OPRF the value). ' +
|
|
93
|
-
'Paired with responseMatches by index — the tool folds ' +
|
|
94
|
-
'each one into the matching entry\'s `extract` in the ' +
|
|
95
|
-
'persisted version, so keep them aligned 1-to-1.',
|
|
96
|
-
items: {
|
|
97
|
-
type: 'object',
|
|
98
|
-
properties: {
|
|
99
|
-
xPath: { type: 'string' },
|
|
100
|
-
jsonPath: { type: 'string' },
|
|
101
|
-
regex: { type: 'string' },
|
|
102
|
-
hash: {
|
|
103
|
-
type: 'string',
|
|
104
|
-
enum: ['oprf', 'oprf-mpc', 'oprf-raw'],
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
paramValues: {
|
|
110
|
-
type: 'object',
|
|
111
|
-
description:
|
|
112
|
-
'Real values for the {{param}} placeholders, ' +
|
|
113
|
-
'e.g. { "username": "Syed" }.',
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
required: ['name', 'url', 'method', 'responseMatches'],
|
|
169
|
+
'Multiple proven drafts → one multi-request version. Each ' +
|
|
170
|
+
'entry has the same shape as `provider`. Pass this OR ' +
|
|
171
|
+
'`provider`, not both.',
|
|
172
|
+
items: PROVIDER_SCHEMA,
|
|
173
|
+
minItems: 1,
|
|
117
174
|
},
|
|
118
175
|
initialUrl: {
|
|
119
176
|
type: 'string',
|
|
@@ -139,25 +196,86 @@ export function createVersionFromCaptureTool(
|
|
|
139
196
|
},
|
|
140
197
|
required: ['major', 'minor', 'patch'],
|
|
141
198
|
},
|
|
199
|
+
jsUserScripts: {
|
|
200
|
+
type: 'string',
|
|
201
|
+
description:
|
|
202
|
+
'Plain JavaScript injected into the provider page before every ' +
|
|
203
|
+
'page load (stored as webSettings.jsUserScripts). Use standard ' +
|
|
204
|
+
'browser APIs (window.location, element.click(), forms) to ' +
|
|
205
|
+
'drive login/navigation so the target request fires — the ' +
|
|
206
|
+
'client already intercepts page requests, and window.Reclaim ' +
|
|
207
|
+
'is only for rare edge cases, not navigation. The Builder ' +
|
|
208
|
+
'verification client installs this to run before every page ' +
|
|
209
|
+
'load (before navigating to initialUrl), matching old-devtool ' +
|
|
210
|
+
'customInjection; window.Reclaim itself is not yet exposed ' +
|
|
211
|
+
'there, so prefer plain browser JS. Guide:\n\n' +
|
|
212
|
+
INJECTION_GUIDE,
|
|
213
|
+
},
|
|
214
|
+
allowedJsRequests: {
|
|
215
|
+
type: 'array',
|
|
216
|
+
description:
|
|
217
|
+
'Extra requests the injection script is allowed to fire ' +
|
|
218
|
+
'(e.g. paginated API calls), as RequestSelectionTemplates. ' +
|
|
219
|
+
'`multiple`/`required` default to true.',
|
|
220
|
+
items: ALLOWED_JS_REQUEST_SCHEMA,
|
|
221
|
+
},
|
|
142
222
|
},
|
|
143
|
-
required: ['providerId', '
|
|
223
|
+
required: ['providerId', 'initialUrl'],
|
|
144
224
|
},
|
|
145
225
|
},
|
|
146
226
|
async(args) => {
|
|
227
|
+
// Exactly one of `provider` / `providers` — normalize to an array.
|
|
228
|
+
const hasMany = Array.isArray(args.providers) && args.providers.length > 0
|
|
229
|
+
const hasOne = !!args.provider
|
|
230
|
+
if(hasMany === hasOne) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
'Pass exactly one of `provider` (a single draft) or ' +
|
|
233
|
+
'`providers` (an array of 1+ drafts).',
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const providers = hasMany ? args.providers! : [args.provider!]
|
|
238
|
+
|
|
147
239
|
// Auto-pick the next version if not explicit
|
|
148
240
|
const ver = args.version ?? (await nextVersion(client, args.providerId))
|
|
149
241
|
|
|
150
|
-
const body = providerToCreateVersionRequest(
|
|
151
|
-
args.
|
|
152
|
-
|
|
153
|
-
args.
|
|
154
|
-
|
|
155
|
-
)
|
|
156
|
-
const result =
|
|
242
|
+
const body = providerToCreateVersionRequest(providers, args.initialUrl, {
|
|
243
|
+
notes: args.notes,
|
|
244
|
+
version: ver,
|
|
245
|
+
jsUserScripts: args.jsUserScripts,
|
|
246
|
+
allowedJsRequests: args.allowedJsRequests,
|
|
247
|
+
})
|
|
248
|
+
const result = await client.call('CreateProviderVersion', {
|
|
157
249
|
params: { providerId: args.providerId },
|
|
158
250
|
body,
|
|
159
|
-
})
|
|
160
|
-
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const created = result.data
|
|
254
|
+
|
|
255
|
+
// Aggregate non-blocking advisories for the dev — nothing here blocks
|
|
256
|
+
// or alters what was published; the author keeps full control.
|
|
257
|
+
const notes: string[] = []
|
|
258
|
+
|
|
259
|
+
// Match/redaction advisory across every request (+ injected requests).
|
|
260
|
+
const requestCounts = [
|
|
261
|
+
...providers.map((p, i) => countsFor(`Request ${i + 1}`, p)),
|
|
262
|
+
...(args.allowedJsRequests ?? []).map(
|
|
263
|
+
(r, i) => countsFor(`Injected request ${i + 1}`, r),
|
|
264
|
+
),
|
|
265
|
+
]
|
|
266
|
+
notes.push(...matchRedactionWarnings(requestCounts))
|
|
267
|
+
|
|
268
|
+
// Let the dev know how/when the script runs (and the window.Reclaim gap).
|
|
269
|
+
if(args.jsUserScripts?.trim()) {
|
|
270
|
+
notes.push(
|
|
271
|
+
'jsUserScripts will run in the Builder verification client before ' +
|
|
272
|
+
'every page load (before navigating to initialUrl), matching ' +
|
|
273
|
+
'old-devtool customInjection. window.Reclaim is not yet exposed ' +
|
|
274
|
+
'there, so prefer plain browser JS for navigation/interaction.',
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return notes.length ? { ...created, _notes: notes } : created
|
|
161
279
|
},
|
|
162
280
|
)
|
|
163
281
|
}
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Optional
|
|
3
|
-
* payloads in the consumer role.
|
|
4
|
-
*
|
|
2
|
+
* Optional eth decryption key — only needed when testing ENCRYPTED callback
|
|
3
|
+
* payloads in the consumer role. The org's results are ECIES-encrypted to its
|
|
4
|
+
* eth public key; this is the matching eth PRIVATE key (`0x`-hex). Resolution:
|
|
5
5
|
*
|
|
6
|
-
* 1.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* openssl genpkey -algorithm X25519 -out reclaim-decrypt.pem
|
|
10
|
-
*
|
|
11
|
-
* 2. Absent — plaintext-only testing (no decryption key; encrypted
|
|
6
|
+
* 1. The `RECLAIM_DECRYPT_KEY` env var (a `0x`-prefixed 32-byte hex key), or
|
|
7
|
+
* 2. A file named `reclaim-decrypt.key` in the project root holding that hex.
|
|
8
|
+
* 3. Absent — plaintext-only testing (no decryption key; encrypted
|
|
12
9
|
* deliveries surface a clear error).
|
|
13
10
|
*
|
|
14
|
-
* The
|
|
15
|
-
*
|
|
16
|
-
* `decryptCallback`.
|
|
11
|
+
* The hex is loaded via the client's `loadDecryptionKey` and passed straight
|
|
12
|
+
* through as `ReclaimConfig.credential` / to `decryptCallback`.
|
|
17
13
|
*/
|
|
18
14
|
import {
|
|
19
15
|
type DecryptionKey,
|
|
@@ -23,34 +19,33 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
23
19
|
import { join } from 'node:path'
|
|
24
20
|
import { findProjectRoot } from '../../../paths.ts'
|
|
25
21
|
|
|
26
|
-
/** Default
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/** Resolve the decrypt-PEM path (the default file in the project root). */
|
|
30
|
-
export function decryptPemPath(
|
|
31
|
-
projectDir = findProjectRoot(process.cwd()),
|
|
32
|
-
): string {
|
|
33
|
-
return join(projectDir, DEFAULT_DECRYPT_PEM)
|
|
34
|
-
}
|
|
22
|
+
/** Default eth decrypt-key filename the agent looks for in the project root. */
|
|
23
|
+
const DEFAULT_DECRYPT_KEY_FILE = 'reclaim-decrypt.key'
|
|
35
24
|
|
|
36
25
|
/**
|
|
37
|
-
* Read the raw decryption-key
|
|
38
|
-
* it. Returns `undefined` when
|
|
26
|
+
* Read the raw eth decryption-key hex — from `RECLAIM_DECRYPT_KEY` or the
|
|
27
|
+
* default file — without loading it. Returns `undefined` when neither is
|
|
28
|
+
* present (plaintext-only).
|
|
39
29
|
*/
|
|
40
|
-
|
|
30
|
+
function resolveDecryptKeyInput(
|
|
41
31
|
projectDir = findProjectRoot(process.cwd()),
|
|
42
32
|
): string | undefined {
|
|
43
|
-
const
|
|
44
|
-
if(
|
|
45
|
-
return
|
|
33
|
+
const fromEnv = process.env.RECLAIM_DECRYPT_KEY?.trim()
|
|
34
|
+
if(fromEnv) {
|
|
35
|
+
return fromEnv
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const keyPath = join(projectDir, DEFAULT_DECRYPT_KEY_FILE)
|
|
39
|
+
if(existsSync(keyPath)) {
|
|
40
|
+
return readFileSync(keyPath, 'utf8').trim()
|
|
46
41
|
}
|
|
47
42
|
|
|
48
43
|
return undefined
|
|
49
44
|
}
|
|
50
45
|
|
|
51
46
|
/**
|
|
52
|
-
* Load the optional decryption key. Returns `undefined` when
|
|
53
|
-
*
|
|
47
|
+
* Load the optional decryption key. Returns `undefined` when no key is
|
|
48
|
+
* configured (the consumer then handles only plaintext deliveries).
|
|
54
49
|
*/
|
|
55
50
|
export async function resolveDecryptionKey(
|
|
56
51
|
projectDir = findProjectRoot(process.cwd()),
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
* Generated/imported keys are written to the project `.env` (mode 0600, and the
|
|
13
13
|
* file is added to `.gitignore`). NO `~/.reclaim` caching. This is a LOCAL
|
|
14
14
|
* signing key only — unrelated to org auth (the org secret) or encryption
|
|
15
|
-
* (the
|
|
15
|
+
* (the eth decrypt key).
|
|
16
16
|
*/
|
|
17
17
|
import { deriveFromPrivateKey } from '@reclaimprotocol/client'
|
|
18
18
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
19
19
|
import { isAbsolute, join } from 'node:path'
|
|
20
|
+
import { privateKey, privateKeyFile } from '../../../consts.ts'
|
|
20
21
|
import { findProjectRoot } from '../../../paths.ts'
|
|
21
22
|
|
|
22
23
|
const ENV_VAR = 'RECLAIM_PRIVATE_KEY'
|
|
@@ -65,10 +66,10 @@ function readDotenvKey(path: string): string | undefined {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/** The explicit raw-hex key-file path, if `RECLAIM_PRIVATE_KEY_FILE` is set. */
|
|
68
|
-
|
|
69
|
+
function ethKeyFilePath(
|
|
69
70
|
projectDir = findProjectRoot(process.cwd()),
|
|
70
71
|
): string | undefined {
|
|
71
|
-
const p =
|
|
72
|
+
const p = privateKeyFile()
|
|
72
73
|
if(!p) {
|
|
73
74
|
return undefined
|
|
74
75
|
}
|
|
@@ -97,7 +98,7 @@ function resolved(
|
|
|
97
98
|
export function resolveEthKey(
|
|
98
99
|
projectDir = findProjectRoot(process.cwd()),
|
|
99
100
|
): ResolvedEthKey | undefined {
|
|
100
|
-
const env =
|
|
101
|
+
const env = privateKey()
|
|
101
102
|
if(env?.trim()) {
|
|
102
103
|
return resolved(normalizeEthHex(env), 'env')
|
|
103
104
|
}
|
|
@@ -120,27 +121,6 @@ export function resolveEthKey(
|
|
|
120
121
|
return undefined
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
/**
|
|
124
|
-
* Resolve the eth proof-owner private key (0x-hex) or throw a clear, actionable
|
|
125
|
-
* error. Used by the proof flow that can't proceed without a signing key.
|
|
126
|
-
*/
|
|
127
|
-
export function requireEthPrivateKey(
|
|
128
|
-
projectDir = findProjectRoot(process.cwd()),
|
|
129
|
-
): string {
|
|
130
|
-
const r = resolveEthKey(projectDir)
|
|
131
|
-
if(!r) {
|
|
132
|
-
throw new Error(
|
|
133
|
-
'No eth proof-owner key found. Set RECLAIM_PRIVATE_KEY (0x-hex), add a '
|
|
134
|
-
+ `RECLAIM_PRIVATE_KEY=0x… line to ${join(projectDir, ENV_FILE)}, or `
|
|
135
|
-
+ 'point RECLAIM_PRIVATE_KEY_FILE at a file holding the raw hex. Run '
|
|
136
|
-
+ 'issue_credentials to generate one, or import_credentials to use a '
|
|
137
|
-
+ 'key you already hold.',
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return r.privateKey
|
|
142
|
-
}
|
|
143
|
-
|
|
144
124
|
/** Append `entry` to the project `.gitignore` if not already present. */
|
|
145
125
|
function ensureGitignored(projectDir: string, entry: string): void {
|
|
146
126
|
const path = join(projectDir, '.gitignore')
|
|
@@ -13,7 +13,7 @@ export function issueTool(): RegisteredTool {
|
|
|
13
13
|
'the project `.env` as RECLAIM_PRIVATE_KEY=0x… (mode 0600; `.env` ' +
|
|
14
14
|
'added to .gitignore). This is a LOCAL signing key only — it is NOT ' +
|
|
15
15
|
'registered with any server and is unrelated to org auth (the org ' +
|
|
16
|
-
'secret) or encryption (the
|
|
16
|
+
'secret) or encryption (the eth decrypt key). ALWAYS confirm with ' +
|
|
17
17
|
'the dev before generating a new key. After writing, run_proof picks ' +
|
|
18
18
|
'it up automatically via resolve_owner_key. The private key is never ' +
|
|
19
19
|
'returned in the response.',
|
|
@@ -5,7 +5,7 @@ import { verificationResultTool } from './result.ts'
|
|
|
5
5
|
* Builder-mode verification tools: higher-level orchestrations over the
|
|
6
6
|
* generated API surface. `get_verification_result` authenticates with the org
|
|
7
7
|
* token, checks status, pulls the stored payload, and decrypts it locally with
|
|
8
|
-
* the optional
|
|
8
|
+
* the optional eth key — a flow the generated raw-API tools can't express
|
|
9
9
|
* (the decryption key never leaves the consumer). Diagnosis is left to the
|
|
10
10
|
* agent over the generated tools (`get_verification_session`,
|
|
11
11
|
* `list_verification_events`, `list_verification_deliveries`), whose
|