@reclaimprotocol/agent 0.1.32 → 0.1.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/authoring/backend.d.ts +13 -0
- package/lib/authoring/index.d.ts +1 -1
- package/lib/authoring/tools.js +78 -42
- package/lib/config/store.d.ts +10 -0
- package/lib/mcp/api/operations.gen.d.ts +247 -4
- package/lib/mcp/api/operations.gen.js +6519 -337
- package/lib/mcp/api/tools.d.ts +10 -2
- package/lib/mcp/api/tools.js +55 -4
- package/lib/mcp/instructions.js +11 -11
- package/lib/mcp/server.d.ts +1 -1
- package/lib/mcp/server.js +3 -0
- package/lib/mcp/start.js +6 -1
- package/lib/mcp/success-response.js +5 -1
- package/lib/mcp/tools/agent/analyze.js +5 -4
- package/lib/mcp/tools/agent/attach.js +11 -7
- package/lib/mcp/tools/agent/build.js +3 -1
- package/lib/mcp/tools/agent/capture.d.ts +2 -2
- package/lib/mcp/tools/agent/capture.js +15 -11
- package/lib/mcp/tools/agent/eval.js +4 -3
- package/lib/mcp/tools/agent/find.js +9 -10
- package/lib/mcp/tools/agent/how-it-works.js +1 -0
- package/lib/mcp/tools/agent/live-view.js +10 -7
- package/lib/mcp/tools/agent/pod-proof.js +7 -11
- package/lib/mcp/tools/agent/propose.js +4 -3
- package/lib/mcp/tools/agent/publish.d.ts +2 -1
- package/lib/mcp/tools/agent/publish.js +12 -6
- package/lib/mcp/tools/agent/replay.js +10 -7
- package/lib/mcp/tools/agent/session.js +16 -8
- package/lib/mcp/tools/agent/shared.js +3 -2
- package/lib/mcp/tools/agent/start-integration.d.ts +15 -0
- package/lib/mcp/tools/agent/start-integration.js +61 -0
- package/lib/mcp/tools/authenticate/authenticate.js +9 -11
- package/lib/mcp/tools/authenticate/build.js +2 -0
- package/lib/mcp/tools/authenticate/builder-access.js +1 -0
- package/lib/mcp/tools/authenticate/eth-key.d.ts +8 -0
- package/lib/mcp/tools/authenticate/eth-key.js +27 -15
- package/lib/mcp/tools/authenticate/import.js +18 -31
- package/lib/mcp/tools/authenticate/issue-org-token.d.ts +8 -0
- package/lib/mcp/tools/authenticate/issue-org-token.js +60 -0
- package/lib/mcp/tools/authenticate/issue.js +9 -9
- package/lib/mcp/tools/authenticate/org-key-store.d.ts +10 -0
- package/lib/mcp/tools/authenticate/org-key-store.js +23 -0
- package/lib/mcp/tools/authenticate/resolve-owner-key.js +10 -12
- package/lib/mcp/tools/mode/mode.js +2 -0
- package/lib/mcp/tools/verification/result.js +52 -12
- package/lib/tools/registry.d.ts +12 -0
- package/lib/tools/registry.js +1 -0
- package/package.json +2 -2
- package/skill/SKILL.builder.md +77 -73
- package/skill/SKILL.common.md +2 -1
- package/skill/builder-mode.md +135 -120
- package/skill/start-integration.md +49 -0
- package/skill/user-script.md +64 -0
- package/skill/verification-client-integration.md +131 -28
- package/src/authoring/backend.ts +15 -0
- package/src/authoring/index.ts +2 -0
- package/src/authoring/tools.ts +86 -42
- package/src/config/store.ts +11 -0
- package/src/mcp/api/operations.gen.ts +6563 -373
- package/src/mcp/api/tools.ts +70 -5
- package/src/mcp/instructions.ts +11 -11
- package/src/mcp/server.ts +4 -0
- package/src/mcp/start.ts +6 -1
- package/src/mcp/success-response.ts +5 -1
- package/src/mcp/tools/agent/analyze.ts +5 -4
- package/src/mcp/tools/agent/attach.ts +11 -7
- package/src/mcp/tools/agent/build.ts +5 -1
- package/src/mcp/tools/agent/capture.ts +20 -12
- package/src/mcp/tools/agent/eval.ts +4 -3
- package/src/mcp/tools/agent/find.ts +9 -10
- package/src/mcp/tools/agent/how-it-works.ts +1 -0
- package/src/mcp/tools/agent/live-view.ts +10 -7
- package/src/mcp/tools/agent/pod-proof.ts +7 -11
- package/src/mcp/tools/agent/propose.ts +4 -3
- package/src/mcp/tools/agent/publish.ts +12 -4
- package/src/mcp/tools/agent/replay.ts +10 -7
- package/src/mcp/tools/agent/session.ts +16 -8
- package/src/mcp/tools/agent/shared.ts +7 -2
- package/src/mcp/tools/agent/start-integration.ts +83 -0
- package/src/mcp/tools/authenticate/authenticate.ts +9 -11
- package/src/mcp/tools/authenticate/build.ts +2 -0
- package/src/mcp/tools/authenticate/builder-access.ts +1 -0
- package/src/mcp/tools/authenticate/eth-key.ts +48 -18
- package/src/mcp/tools/authenticate/import.ts +21 -36
- package/src/mcp/tools/authenticate/issue-org-token.ts +84 -0
- package/src/mcp/tools/authenticate/issue.ts +9 -9
- package/src/mcp/tools/authenticate/org-key-store.ts +25 -0
- package/src/mcp/tools/authenticate/resolve-owner-key.ts +10 -12
- package/src/mcp/tools/mode/mode.ts +2 -0
- package/src/mcp/tools/verification/result.ts +58 -13
- package/src/tools/registry.ts +14 -0
package/src/mcp/api/tools.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { unauthorized } from '@hapi/boom'
|
|
1
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
-
import
|
|
3
|
+
import { ReclaimClient } from '@reclaimprotocol/client/api'
|
|
3
4
|
import type {
|
|
4
5
|
IOperationArgs,
|
|
5
6
|
IOperationId
|
|
6
7
|
} from '@reclaimprotocol/client/openapi'
|
|
8
|
+
import assert from 'node:assert'
|
|
9
|
+
import { orgSecret } from '../../consts.ts'
|
|
7
10
|
import { remapErrorAsResponse } from '../remap_errors.ts'
|
|
8
11
|
import { successResponse } from '../success-response.ts'
|
|
12
|
+
import { savedOrgKey } from '../tools/authenticate/org-key-store.ts'
|
|
9
13
|
import { MCP_TOOLS } from './operations.gen.ts'
|
|
10
14
|
|
|
11
15
|
/**
|
|
@@ -22,8 +26,34 @@ function toSnakeCase(name: string): string {
|
|
|
22
26
|
.toLowerCase()
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
/** The client for a call that presents the organization's API key. */
|
|
30
|
+
export type OrgClientFactory = (orgToken: string) => ReclaimClient
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Register a tool per generated operation. Calls go out with the developer's
|
|
34
|
+
* session token, except operations only an org API key (`rorg_…`) authorizes:
|
|
35
|
+
* those take an `orgToken` input (else the server's RECLAIM_ORG_SECRET) and go
|
|
36
|
+
* out through `orgClient` with that key.
|
|
37
|
+
*/
|
|
38
|
+
export function registerTools(
|
|
39
|
+
server: McpServer,
|
|
40
|
+
client: ReclaimClient,
|
|
41
|
+
orgClient: OrgClientFactory = (orgToken) => new ReclaimClient({
|
|
42
|
+
baseUrl: client.baseUrl,
|
|
43
|
+
token: orgToken,
|
|
44
|
+
}),
|
|
45
|
+
) {
|
|
46
|
+
for(const [opId, tool] of Object.entries(MCP_TOOLS)) {
|
|
47
|
+
const orgTokenOnly = 'orgTokenOnly' in tool && tool.orgTokenOnly === true
|
|
48
|
+
const shape = {
|
|
49
|
+
description: tool.description,
|
|
50
|
+
inputSchema: tool.inputSchema,
|
|
51
|
+
// Generated entries gain `annotations` when the spec marks them; read
|
|
52
|
+
// it defensively so an older generated file still registers.
|
|
53
|
+
...('annotations' in tool && tool.annotations
|
|
54
|
+
? { annotations: tool.annotations }
|
|
55
|
+
: {}),
|
|
56
|
+
}
|
|
27
57
|
if(!isOperationId(opId)) {
|
|
28
58
|
throw new Error(
|
|
29
59
|
`Generated MCP operation is not in the API client: ${opId}`,
|
|
@@ -37,13 +67,21 @@ export function registerTools(server: McpServer, client: ReclaimClient) {
|
|
|
37
67
|
server.registerTool(
|
|
38
68
|
toSnakeCase(opId),
|
|
39
69
|
shape,
|
|
40
|
-
async(
|
|
70
|
+
async(input: unknown) => {
|
|
41
71
|
try {
|
|
72
|
+
if(!isRecord(input)) {
|
|
73
|
+
throw new Error(`Invalid generated MCP arguments for ${opId}`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const { orgToken, ...args } = input
|
|
42
77
|
if(!isOperationArgs(opId, args)) {
|
|
43
78
|
throw new Error(`Invalid generated MCP arguments for ${opId}`)
|
|
44
79
|
}
|
|
45
80
|
|
|
46
|
-
const
|
|
81
|
+
const caller = orgTokenOnly
|
|
82
|
+
? orgClient(orgTokenFor(toSnakeCase(opId), orgToken, args))
|
|
83
|
+
: client
|
|
84
|
+
const result = await caller.call(
|
|
47
85
|
opId,
|
|
48
86
|
args,
|
|
49
87
|
)
|
|
@@ -79,3 +117,30 @@ function isOperationArgs<T extends IOperationId>(
|
|
|
79
117
|
): value is IOperationArgs<T> {
|
|
80
118
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
81
119
|
}
|
|
120
|
+
|
|
121
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
122
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The org API key a call authenticates with: its `orgToken`, else the
|
|
126
|
+
* server's RECLAIM_ORG_SECRET, else the key `issue_org_token` saved (for the
|
|
127
|
+
* call's `orgId` when it has one, otherwise the most recent). The session
|
|
128
|
+
* token (the developer's sign-in) can't stand in, since these routes accept
|
|
129
|
+
* only an org key. */
|
|
130
|
+
function orgTokenFor(tool: string, orgToken: unknown, args: unknown) {
|
|
131
|
+
const orgId = isRecord(args) && isRecord(args.params)
|
|
132
|
+
? args.params.orgId
|
|
133
|
+
: undefined
|
|
134
|
+
const token = (typeof orgToken === 'string' && orgToken)
|
|
135
|
+
|| orgSecret()
|
|
136
|
+
|| savedOrgKey(typeof orgId === 'string' ? orgId : undefined)
|
|
137
|
+
assert(
|
|
138
|
+
token,
|
|
139
|
+
unauthorized(
|
|
140
|
+
`${tool} authenticates with the organization's API key. Run `
|
|
141
|
+
+ 'issue_org_token to save one, or pass orgToken, or set '
|
|
142
|
+
+ 'RECLAIM_ORG_SECRET for the MCP server.',
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
return token
|
|
146
|
+
}
|
package/src/mcp/instructions.ts
CHANGED
|
@@ -56,7 +56,7 @@ const BUILDER_TOPIC_FILES: Record<string, string> = {
|
|
|
56
56
|
* the right one without fetching several. Keyed across both modes; only the
|
|
57
57
|
* topics a mode actually has are listed back to it. */
|
|
58
58
|
const TOPIC_SUMMARIES: Record<string, string> = {
|
|
59
|
-
'overview': 'Terminology, your stance, and the
|
|
59
|
+
'overview': 'Terminology, your stance, and the authoring checklist.',
|
|
60
60
|
'contributions': 'Contributing a public provider as a volunteer: the '
|
|
61
61
|
+ 'candidate-and-review workflow and the extra rules it adds.',
|
|
62
62
|
'auth': 'Signing in to the backend, and which token authorizes what.',
|
|
@@ -65,23 +65,23 @@ const TOPIC_SUMMARIES: Record<string, string> = {
|
|
|
65
65
|
'browser': 'Choosing a browser source (local Chrome, the Reclaim runtime in '
|
|
66
66
|
+ 'Docker, a remote Builder browser, or a custom CDP endpoint), '
|
|
67
67
|
+ 'geolocation, live view, and launch troubleshooting.',
|
|
68
|
-
'capture': 'Tabs,
|
|
69
|
-
+ 'authenticated request to prove.',
|
|
68
|
+
'capture': 'Tabs, sign-in detection, capturing through the navigation '
|
|
69
|
+
+ 'script, and picking an authenticated request to prove.',
|
|
70
70
|
'draft': 'The OPRF/privacy decision, propose_provider, and the canonical '
|
|
71
71
|
+ 'drafted shape.',
|
|
72
72
|
'params': 'Extracted vs consumer-supplied vs secret params, the URL '
|
|
73
73
|
+ 'character budget, and runtime resolution.',
|
|
74
74
|
'prove': 'replay_request, the mandatory auth-bound check, run_proof, and '
|
|
75
75
|
+ 'diagnosing failures.',
|
|
76
|
-
'publish': 'Creating
|
|
77
|
-
+ 'and going live.',
|
|
78
|
-
'discover': '
|
|
79
|
-
+ 'session.',
|
|
80
|
-
'integration': '
|
|
81
|
-
+ 'over the HTTP session API;
|
|
76
|
+
'publish': 'Creating a provider, provider versions, multi-request '
|
|
77
|
+
+ 'providers, and going live.',
|
|
78
|
+
'discover': 'Reusing existing providers: finding, resolving, and '
|
|
79
|
+
+ 'creating a verification session.',
|
|
80
|
+
'integration': 'After start_integration: org key, callback, frontend and '
|
|
81
|
+
+ 'backend over the HTTP session API; `api=2` and result '
|
|
82
82
|
+ 'verification.',
|
|
83
|
-
'user-script': '
|
|
84
|
-
+ '
|
|
83
|
+
'user-script': 'The navigation script (jsUserScripts): sign-in redirect, '
|
|
84
|
+
+ 'multi-page walk, test_user_script with keep.',
|
|
85
85
|
'interception': 'injectionType / interceptorType, live interception, the '
|
|
86
86
|
+ 'document-replay flag, and what to do when a site won\'t load or '
|
|
87
87
|
+ 'won\'t let the user sign in.',
|
package/src/mcp/server.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { successResponse } from './success-response.ts'
|
|
|
10
10
|
export {
|
|
11
11
|
defineTool,
|
|
12
12
|
type RegisteredTool,
|
|
13
|
+
type ToolAnnotations,
|
|
13
14
|
type ToolDefinition,
|
|
14
15
|
} from '../tools/registry.ts'
|
|
15
16
|
|
|
@@ -25,6 +26,9 @@ export function registerToolList(server: McpServer, tools: RegisteredTool[]) {
|
|
|
25
26
|
{
|
|
26
27
|
description: tool.definition.description,
|
|
27
28
|
inputSchema: toZod(tool.definition.inputSchema),
|
|
29
|
+
...(tool.definition.annotations
|
|
30
|
+
? { annotations: tool.definition.annotations }
|
|
31
|
+
: {}),
|
|
28
32
|
},
|
|
29
33
|
async(args) => {
|
|
30
34
|
try {
|
package/src/mcp/start.ts
CHANGED
|
@@ -116,7 +116,12 @@ export async function startMcpServer() {
|
|
|
116
116
|
|
|
117
117
|
// Add auto-generated tools with snake_case wrappers
|
|
118
118
|
// for every MCP-tagged operation.
|
|
119
|
-
|
|
119
|
+
// Org-key-only operations go out with the call's `orgToken` instead.
|
|
120
|
+
registerTools(server, client, (orgToken) => new ReclaimClient({
|
|
121
|
+
baseUrl: apiUrl(),
|
|
122
|
+
token: orgToken,
|
|
123
|
+
headers: { 'User-Agent': `${NAME}/${VERSION}` },
|
|
124
|
+
}))
|
|
120
125
|
|
|
121
126
|
if(agentToolsEnabled) {
|
|
122
127
|
agentState = buildAgentState()
|
|
@@ -8,7 +8,11 @@ export function successResponse(value: unknown): CallToolResult {
|
|
|
8
8
|
structuredContent: structured(value),
|
|
9
9
|
content: [{
|
|
10
10
|
type: 'text',
|
|
11
|
-
text
|
|
11
|
+
// A string result is text written for the agent (a plan, a guide):
|
|
12
|
+
// JSON-quoting it would escape every newline.
|
|
13
|
+
text: typeof value === 'string'
|
|
14
|
+
? value
|
|
15
|
+
: JSON.stringify(value, null, 2) ?? 'null',
|
|
12
16
|
}],
|
|
13
17
|
}
|
|
14
18
|
}
|
|
@@ -7,10 +7,10 @@ export function analyzeTool(captures: CaptureStore): RegisteredTool {
|
|
|
7
7
|
{
|
|
8
8
|
name: 'analyze_request_constraints',
|
|
9
9
|
description:
|
|
10
|
-
'
|
|
11
|
-
+ 'request, CSRF dependency, bot challenge, short-lived
|
|
12
|
-
+ 'GraphQL. Call it on each top candidate from '
|
|
13
|
-
+ 'find_requests_containing before drafting, and relay the
|
|
10
|
+
'Returns provider-authoring concerns for one captured request: '
|
|
11
|
+
+ 'signed request, CSRF dependency, bot challenge, short-lived '
|
|
12
|
+
+ 'token, or GraphQL. Call it on each top candidate from '
|
|
13
|
+
+ '`find_requests_containing` before drafting, and relay the '
|
|
14
14
|
+ '`concerns` to the developer verbatim.',
|
|
15
15
|
inputSchema: {
|
|
16
16
|
type: 'object',
|
|
@@ -20,6 +20,7 @@ export function analyzeTool(captures: CaptureStore): RegisteredTool {
|
|
|
20
20
|
},
|
|
21
21
|
required: ['captureId', 'requestId'],
|
|
22
22
|
},
|
|
23
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
23
24
|
},
|
|
24
25
|
async({ captureId, requestId }) => {
|
|
25
26
|
const entry = captures.sessions.get(captureId)
|
|
@@ -197,13 +197,16 @@ export function attachTool(
|
|
|
197
197
|
{
|
|
198
198
|
name: 'attach_browser',
|
|
199
199
|
description:
|
|
200
|
-
'Connects
|
|
201
|
-
+ '
|
|
202
|
-
+ '
|
|
203
|
-
+ '
|
|
204
|
-
+ '
|
|
205
|
-
+ '
|
|
206
|
-
+ '
|
|
200
|
+
'Connects the authoring tools to a browser that loads the '
|
|
201
|
+
+ 'developer\'s sign-in page and target site. `builder` and '
|
|
202
|
+
+ '`container` run the browser remotely or in Docker, away from the '
|
|
203
|
+
+ 'developer\'s own profile; `dedicated`, `attach`, and `custom` use a '
|
|
204
|
+
+ 'local or supplied Chrome. Call without `mode` to receive the '
|
|
205
|
+
+ 'options, ask the developer to choose, then call again once with '
|
|
206
|
+
+ 'the chosen mode. `builder` consumes quota. Only `container` and '
|
|
207
|
+
+ '`builder` support a shareable live view. Relay returned live-view '
|
|
208
|
+
+ 'URLs verbatim. See how_it_works({ topic: "browser" }) for '
|
|
209
|
+
+ 'selection, geolocation, and lifecycle.',
|
|
207
210
|
inputSchema: {
|
|
208
211
|
type: 'object',
|
|
209
212
|
properties: {
|
|
@@ -249,6 +252,7 @@ export function attachTool(
|
|
|
249
252
|
},
|
|
250
253
|
},
|
|
251
254
|
},
|
|
255
|
+
annotations: { openWorldHint: true, destructiveHint: false },
|
|
252
256
|
},
|
|
253
257
|
async(parsed) => {
|
|
254
258
|
const persisted = state.config.read()?.browser_agent
|
|
@@ -24,6 +24,7 @@ import { podProofTools } from './pod-proof.ts'
|
|
|
24
24
|
import { createVersionFromCaptureTool } from './publish.ts'
|
|
25
25
|
import { resetAll, sessionTools } from './session.ts'
|
|
26
26
|
import { sharedAuthoringTools } from './shared.ts'
|
|
27
|
+
import { startIntegrationTool } from './start-integration.ts'
|
|
27
28
|
|
|
28
29
|
export interface AgentState {
|
|
29
30
|
attach: { current?: AttachState, config: ConfigStore }
|
|
@@ -90,7 +91,10 @@ export function buildAgentTools(
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
if(client) {
|
|
93
|
-
tools.push(
|
|
94
|
+
tools.push(startIntegrationTool())
|
|
95
|
+
tools.push(createVersionFromCaptureTool(
|
|
96
|
+
client, state.attach, state.captures,
|
|
97
|
+
))
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
return tools
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type DraftStore,
|
|
3
|
+
type KeptScriptRef,
|
|
4
|
+
makeDraftStore,
|
|
5
|
+
} from '../../../authoring/index.ts'
|
|
2
6
|
import { type CaptureSession, startCapture } from '../../../cdp/capture.ts'
|
|
3
7
|
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
4
8
|
import type { AttachState } from './attach.ts'
|
|
@@ -15,7 +19,7 @@ export interface CaptureEntry {
|
|
|
15
19
|
drafts: DraftStore
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
export interface CaptureStore {
|
|
22
|
+
export interface CaptureStore extends KeptScriptRef {
|
|
19
23
|
sessions: Map<string, CaptureEntry>
|
|
20
24
|
activeTab?: string
|
|
21
25
|
}
|
|
@@ -27,6 +31,7 @@ export async function resetCaptures(captures: CaptureStore) {
|
|
|
27
31
|
|
|
28
32
|
captures.sessions.clear()
|
|
29
33
|
delete captures.activeTab
|
|
34
|
+
delete captures.keptScript
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export function captureTools(
|
|
@@ -45,14 +50,15 @@ export function captureTools(
|
|
|
45
50
|
{
|
|
46
51
|
name: 'start_capture',
|
|
47
52
|
description:
|
|
48
|
-
'
|
|
49
|
-
+ '
|
|
50
|
-
+ '
|
|
53
|
+
'Starts recording network requests on a tab. Returns a captureId, '
|
|
54
|
+
+ 'the session handle that the authoring tools (`navigate`, '
|
|
55
|
+
+ '`propose_provider`, `run_proof`) take.',
|
|
51
56
|
inputSchema: {
|
|
52
57
|
type: 'object',
|
|
53
58
|
properties: { tabId: { type: 'string' } },
|
|
54
59
|
required: ['tabId'],
|
|
55
60
|
},
|
|
61
|
+
annotations: { openWorldHint: true },
|
|
56
62
|
},
|
|
57
63
|
async({ tabId }) => {
|
|
58
64
|
const attached = requireAttached()
|
|
@@ -70,13 +76,14 @@ export function captureTools(
|
|
|
70
76
|
{
|
|
71
77
|
name: 'stop_capture',
|
|
72
78
|
description:
|
|
73
|
-
'
|
|
74
|
-
+ '
|
|
79
|
+
'Stops recording on a capture. Captured requests stay queryable '
|
|
80
|
+
+ 'until `reset_session`.',
|
|
75
81
|
inputSchema: {
|
|
76
82
|
type: 'object',
|
|
77
83
|
properties: { captureId: { type: 'string' } },
|
|
78
84
|
required: ['captureId'],
|
|
79
85
|
},
|
|
86
|
+
annotations: { openWorldHint: false, idempotentHint: true },
|
|
80
87
|
},
|
|
81
88
|
async({ captureId }) => {
|
|
82
89
|
const entry = captures.sessions.get(captureId)
|
|
@@ -93,11 +100,11 @@ export function captureTools(
|
|
|
93
100
|
{
|
|
94
101
|
name: 'get_request',
|
|
95
102
|
description:
|
|
96
|
-
'
|
|
97
|
-
+ '
|
|
98
|
-
+ '
|
|
99
|
-
+ '
|
|
100
|
-
+ '
|
|
103
|
+
'Returns the full headers and bodies of one captured request, to '
|
|
104
|
+
+ 'inspect a candidate that `find_requests_containing` ranked. Unlike '
|
|
105
|
+
+ 'the other tools, headers are raw: cookie and authorization values '
|
|
106
|
+
+ 'are visible. Read them for diagnosis; do not repeat them to the '
|
|
107
|
+
+ 'developer or write them to a file.',
|
|
101
108
|
inputSchema: {
|
|
102
109
|
type: 'object',
|
|
103
110
|
properties: {
|
|
@@ -106,6 +113,7 @@ export function captureTools(
|
|
|
106
113
|
},
|
|
107
114
|
required: ['captureId', 'requestId'],
|
|
108
115
|
},
|
|
116
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
109
117
|
},
|
|
110
118
|
async({ captureId, requestId }) => {
|
|
111
119
|
const entry = captures.sessions.get(captureId)
|
|
@@ -11,10 +11,11 @@ export function listTabsTool(attachRef: {
|
|
|
11
11
|
{
|
|
12
12
|
name: 'list_tabs',
|
|
13
13
|
description:
|
|
14
|
-
'
|
|
15
|
-
+ 'and URL. Call it after attach_browser to pick the tabId
|
|
16
|
-
+ 'start_capture
|
|
14
|
+
'Returns the open tabs of the attached browser, each with its tabId, '
|
|
15
|
+
+ 'title, and URL. Call it after `attach_browser` to pick the tabId '
|
|
16
|
+
+ 'for `start_capture`.',
|
|
17
17
|
inputSchema: { type: 'object', properties: {} },
|
|
18
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
18
19
|
},
|
|
19
20
|
async() => {
|
|
20
21
|
if(!attachRef.current) {
|
|
@@ -18,20 +18,19 @@ export function findTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
18
18
|
{
|
|
19
19
|
name: 'find_requests_containing',
|
|
20
20
|
description:
|
|
21
|
-
'
|
|
22
|
-
+ 'URL
|
|
23
|
-
+ '
|
|
24
|
-
+ '
|
|
25
|
-
+ '`value`
|
|
26
|
-
+ '
|
|
27
|
-
+ '
|
|
28
|
-
+ '
|
|
29
|
-
+ 'per-field (a redaction per part) rather than as one string. '
|
|
30
|
-
+ 'Server-side; bodies stay here.',
|
|
21
|
+
'Searches captured responses for any encoding of `value` (raw, '
|
|
22
|
+
+ 'URL-, JSON-, or HTML-escaped). Returns ranked candidates with the '
|
|
23
|
+
+ 'matched variant, score, and a short snippet, to locate the '
|
|
24
|
+
+ 'request that returns a data point. Also finds split matches: when '
|
|
25
|
+
+ '`value` has several words (for example, a full name) and a '
|
|
26
|
+
+ 'response carries each word in a separate field, the candidate has '
|
|
27
|
+
+ '`splitMatch: true`; prove those per field (one redaction per '
|
|
28
|
+
+ 'part). Response bodies stay server-side.',
|
|
31
29
|
inputSchema: objectSchema(
|
|
32
30
|
{ value: { type: 'string' }, limit: { type: 'number' } },
|
|
33
31
|
['value'],
|
|
34
32
|
),
|
|
33
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
35
34
|
},
|
|
36
35
|
async(args) => {
|
|
37
36
|
const backend = resolveBackend(args)
|
|
@@ -20,6 +20,7 @@ export function howItWorksTool(oldMode: boolean): RegisteredTool {
|
|
|
20
20
|
+ 'backend. Call without a topic for the overview and index. Before an '
|
|
21
21
|
+ 'unfamiliar or sensitive step, request that topic. Prefer one topic; '
|
|
22
22
|
+ '`all` returns the much larger complete guide.',
|
|
23
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
23
24
|
inputSchema: {
|
|
24
25
|
type: 'object',
|
|
25
26
|
properties: {
|
|
@@ -27,13 +27,15 @@ export function liveViewTools(
|
|
|
27
27
|
{
|
|
28
28
|
name: 'share_browser_view',
|
|
29
29
|
description:
|
|
30
|
-
'
|
|
31
|
-
+ '
|
|
32
|
-
+ '
|
|
33
|
-
+ '
|
|
34
|
-
+ '
|
|
35
|
-
+ '
|
|
36
|
-
+ '
|
|
30
|
+
'Returns a link that lets someone who is not at this machine watch '
|
|
31
|
+
+ 'and drive the attached `container` browser, for example to sign '
|
|
32
|
+
+ 'in. This server opens no tunnel itself. Step 1: call with no '
|
|
33
|
+
+ 'arguments to get the local viewer URL and the `cloudflared` '
|
|
34
|
+
+ 'command for the developer to run. Step 2: after cloudflared '
|
|
35
|
+
+ 'prints its `https://*.trycloudflare.com` URL, call again with it '
|
|
36
|
+
+ 'as `publicUrl` to get the link to relay. The link grants access '
|
|
37
|
+
+ 'to that browser session only, so share it only with the person '
|
|
38
|
+
+ 'who needs it. For `builder`, use the `liveViewUrl` from '
|
|
37
39
|
+ '`attach_browser`.',
|
|
38
40
|
inputSchema: {
|
|
39
41
|
type: 'object',
|
|
@@ -45,6 +47,7 @@ export function liveViewTools(
|
|
|
45
47
|
},
|
|
46
48
|
},
|
|
47
49
|
},
|
|
50
|
+
annotations: { openWorldHint: true, idempotentHint: true },
|
|
48
51
|
},
|
|
49
52
|
async(parsed) => {
|
|
50
53
|
const attached = attachRef.current
|
|
@@ -31,17 +31,12 @@ export function podProofTools(
|
|
|
31
31
|
{
|
|
32
32
|
name: 'run_proof_via_pod',
|
|
33
33
|
description:
|
|
34
|
-
'
|
|
35
|
-
+ '(/reclaim/prove) instead of the local attestor SDK
|
|
36
|
-
+ '
|
|
37
|
-
+ '
|
|
38
|
-
+ '
|
|
39
|
-
+ '
|
|
40
|
-
+ 'held the browser\'s TLS session (and its country-routed egress, '
|
|
41
|
-
+ 'if any), so '
|
|
42
|
-
+ 'the proof reaches the target from the browser\'s own exit IP. '
|
|
43
|
-
+ 'No ownerAddress needed — the pod signs with the attestor\'s '
|
|
44
|
-
+ 'own TEE key, not a local credential.',
|
|
34
|
+
'Proves the drafted recipe through the remote browser pod\'s own '
|
|
35
|
+
+ 'TEE (/reclaim/prove) instead of the local attestor SDK. Use it '
|
|
36
|
+
+ 'instead of `run_proof` when the attach mode is "builder": the '
|
|
37
|
+
+ 'proof then reaches the target from the browser\'s own exit IP and '
|
|
38
|
+
+ 'country. Needs no `ownerAddress`; the pod signs with its own TEE '
|
|
39
|
+
+ 'key.',
|
|
45
40
|
inputSchema: {
|
|
46
41
|
type: 'object',
|
|
47
42
|
properties: {
|
|
@@ -50,6 +45,7 @@ export function podProofTools(
|
|
|
50
45
|
},
|
|
51
46
|
required: ['captureId', 'draftId'],
|
|
52
47
|
},
|
|
48
|
+
annotations: { openWorldHint: true, destructiveHint: false },
|
|
53
49
|
},
|
|
54
50
|
async({ captureId, draftId }) => {
|
|
55
51
|
const apiUrl = attachRef.current?.apiUrl
|
|
@@ -18,9 +18,9 @@ export function proposeTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
18
18
|
name: 'propose_provider',
|
|
19
19
|
description:
|
|
20
20
|
'Drafts a provider from one authenticated captured request and '
|
|
21
|
-
+ 'keeps captured secrets server-side. Use `targets` for
|
|
22
|
-
+ '
|
|
23
|
-
+ 'and secret-free recipe. Set `shortenNames: false` to preserve an '
|
|
21
|
+
+ 'keeps captured secrets server-side. Use `targets` for the data '
|
|
22
|
+
+ 'points the claim needs from that response; returns a `draftId` '
|
|
23
|
+
+ 'and a secret-free recipe. Set `shortenNames: false` to preserve an '
|
|
24
24
|
+ 'explicit parameter name, including a hidden `REQ_` or `URL_` '
|
|
25
25
|
+ 'prefix. For matching and naming guidance, call '
|
|
26
26
|
+ 'how_it_works({ topic: "draft" }) and topic `params`.',
|
|
@@ -63,6 +63,7 @@ export function proposeTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
63
63
|
},
|
|
64
64
|
['requestId', 'name'],
|
|
65
65
|
),
|
|
66
|
+
annotations: { openWorldHint: false, destructiveHint: false },
|
|
66
67
|
},
|
|
67
68
|
async(args) => {
|
|
68
69
|
const backend = resolveBackend(args)
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
} from '@reclaimprotocol/client/openapi'
|
|
6
6
|
import assert from 'node:assert'
|
|
7
7
|
import type { JSONSchema } from 'zod/v4/core'
|
|
8
|
+
import type { KeptScriptRef } from '../../../authoring/index.ts'
|
|
8
9
|
import {
|
|
9
10
|
ALLOWED_REQUESTS_HINT,
|
|
10
11
|
COOKIE_ATTACHMENT_HINT,
|
|
@@ -232,13 +233,16 @@ interface PublishArgs {
|
|
|
232
233
|
export function createVersionFromCaptureTool(
|
|
233
234
|
client: ReclaimClient,
|
|
234
235
|
attachRef?: { current?: AttachState },
|
|
236
|
+
kept?: KeptScriptRef,
|
|
235
237
|
): RegisteredTool {
|
|
236
238
|
return defineTool<PublishArgs>(
|
|
237
239
|
{
|
|
238
240
|
name: 'create_provider_version_from_capture',
|
|
239
241
|
description:
|
|
240
242
|
'Creates a new immutable provider version after `run_proof` succeeds. '
|
|
241
|
-
+ '
|
|
243
|
+
+ 'Publishes the navigation script kept by `test_user_script` as '
|
|
244
|
+
+ '`jsUserScripts` unless `jsUserScripts` is passed. Pass exactly '
|
|
245
|
+
+ 'one `provider` or `providers` value returned by '
|
|
242
246
|
+ '`propose_provider`; do not rebuild the recipe. The default is a '
|
|
243
247
|
+ 'patch bump, or use `bump` or an explicitly requested higher '
|
|
244
248
|
+ '`version`. The new version remains a draft until published or '
|
|
@@ -306,6 +310,8 @@ export function createVersionFromCaptureTool(
|
|
|
306
310
|
jsUserScripts: {
|
|
307
311
|
type: 'string',
|
|
308
312
|
description:
|
|
313
|
+
'Defaults to the script kept by `test_user_script` with ' +
|
|
314
|
+
'`keep: true`; pass it only to override. ' +
|
|
309
315
|
USER_SCRIPT_HINT +
|
|
310
316
|
' Stored as webSettings.jsUserScripts; the Builder ' +
|
|
311
317
|
'verification client runs it before every page load ' +
|
|
@@ -369,8 +375,10 @@ export function createVersionFromCaptureTool(
|
|
|
369
375
|
},
|
|
370
376
|
required: ['providerId', 'initialUrl'],
|
|
371
377
|
},
|
|
378
|
+
annotations: { openWorldHint: true, destructiveHint: false },
|
|
372
379
|
},
|
|
373
380
|
async(args) => {
|
|
381
|
+
const jsUserScripts = args.jsUserScripts ?? kept?.keptScript?.script
|
|
374
382
|
// Exactly one of `provider` / `providers` — normalize to an array.
|
|
375
383
|
const hasMany = Array.isArray(args.providers) && args.providers.length > 0
|
|
376
384
|
const hasOne = !!args.provider
|
|
@@ -396,7 +404,7 @@ export function createVersionFromCaptureTool(
|
|
|
396
404
|
notes: args.notes,
|
|
397
405
|
version: ver,
|
|
398
406
|
requiredContext: args.requiredContext,
|
|
399
|
-
jsUserScripts
|
|
407
|
+
jsUserScripts,
|
|
400
408
|
allowedJsRequests: args.allowedJsRequests,
|
|
401
409
|
browserRequestActions: args.browserRequestActions,
|
|
402
410
|
interceptorOptions: args.interceptorOptions,
|
|
@@ -428,12 +436,12 @@ export function createVersionFromCaptureTool(
|
|
|
428
436
|
const attached = attachRef?.current
|
|
429
437
|
notes.push(...postPublishBrowserNotes(attached, {
|
|
430
438
|
initialUrl: args.initialUrl,
|
|
431
|
-
hasUserScript: !!
|
|
439
|
+
hasUserScript: !!jsUserScripts?.trim(),
|
|
432
440
|
hasInterceptor: !!args.interceptorOptions,
|
|
433
441
|
}))
|
|
434
442
|
|
|
435
443
|
// Let the developer know how and when the script runs.
|
|
436
|
-
if(
|
|
444
|
+
if(jsUserScripts?.trim()) {
|
|
437
445
|
notes.push(
|
|
438
446
|
'jsUserScripts will run in the Builder verification client before ' +
|
|
439
447
|
'every page load (before navigating to initialUrl), matching ' +
|
|
@@ -13,13 +13,15 @@ export function replayTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
13
13
|
{
|
|
14
14
|
name: 'replay_request',
|
|
15
15
|
description:
|
|
16
|
-
'
|
|
17
|
-
+ '
|
|
18
|
-
+ '
|
|
19
|
-
+ '
|
|
20
|
-
+ '
|
|
21
|
-
+ '
|
|
22
|
-
+ '
|
|
16
|
+
'Re-sends the drafted request from inside the attached authoring '
|
|
17
|
+
+ 'browser (not from this machine) and checks that its selectors '
|
|
18
|
+
+ 'extract the target data points. Captured secrets stay server-side '
|
|
19
|
+
+ 'and are not returned. Returns diagnostics. Run it before '
|
|
20
|
+
+ '`run_proof`. With `withoutSecrets: true`, sends the request with '
|
|
21
|
+
+ 'no cookies or auth headers, like an anonymous visitor. If the '
|
|
22
|
+
+ 'values still extract (`valid: true`), the endpoint is public and '
|
|
23
|
+
+ 'the proof would not bind to the user: choose an auth-bound '
|
|
24
|
+
+ 'request instead.',
|
|
23
25
|
inputSchema: objectSchema(
|
|
24
26
|
{
|
|
25
27
|
draftId: { type: 'string' },
|
|
@@ -27,6 +29,7 @@ export function replayTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
27
29
|
},
|
|
28
30
|
['draftId'],
|
|
29
31
|
),
|
|
32
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
30
33
|
},
|
|
31
34
|
async(args) => {
|
|
32
35
|
const backend = resolveBackend(args)
|