@reclaimprotocol/agent 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/lib/authoring/backend.d.ts +0 -24
  2. package/lib/authoring/find.d.ts +3 -0
  3. package/lib/authoring/find.js +54 -0
  4. package/lib/authoring/index.d.ts +3 -3
  5. package/lib/authoring/index.js +4 -3
  6. package/lib/authoring/propose.d.ts +3 -0
  7. package/lib/authoring/propose.js +109 -0
  8. package/lib/authoring/replay.d.ts +10 -0
  9. package/lib/authoring/replay.js +34 -0
  10. package/lib/authoring/tools.d.ts +13 -6
  11. package/lib/authoring/tools.js +108 -293
  12. package/lib/authoring/util.d.ts +27 -0
  13. package/lib/authoring/util.js +21 -0
  14. package/lib/consts.d.ts +6 -1
  15. package/lib/consts.js +9 -2
  16. package/lib/mcp/server.d.ts +2 -41
  17. package/lib/mcp/server.js +6 -34
  18. package/lib/mcp/start.js +4 -2
  19. package/lib/mcp/tools/agent/analyze.js +3 -3
  20. package/lib/mcp/tools/agent/build.d.ts +4 -4
  21. package/lib/mcp/tools/agent/build.js +13 -15
  22. package/lib/mcp/tools/agent/capture.d.ts +13 -1
  23. package/lib/mcp/tools/agent/capture.js +18 -85
  24. package/lib/mcp/tools/agent/eval.d.ts +5 -2
  25. package/lib/mcp/tools/agent/eval.js +11 -121
  26. package/lib/mcp/tools/agent/find.d.ts +10 -3
  27. package/lib/mcp/tools/agent/find.js +25 -31
  28. package/lib/mcp/tools/agent/proof.d.ts +13 -3
  29. package/lib/mcp/tools/agent/proof.js +81 -132
  30. package/lib/mcp/tools/agent/propose.d.ts +10 -9
  31. package/lib/mcp/tools/agent/propose.js +87 -64
  32. package/lib/mcp/tools/agent/replay.d.ts +10 -3
  33. package/lib/mcp/tools/agent/replay.js +29 -36
  34. package/lib/mcp/tools/agent/session.d.ts +1 -3
  35. package/lib/mcp/tools/agent/session.js +5 -5
  36. package/lib/mcp/tools/agent/shared.d.ts +5 -3
  37. package/lib/mcp/tools/agent/shared.js +29 -19
  38. package/lib/old/client.d.ts +39 -0
  39. package/lib/old/client.js +18 -0
  40. package/lib/old/login-cdp.d.ts +24 -5
  41. package/lib/old/login-cdp.js +29 -15
  42. package/lib/old/sdk-client.d.ts +39 -0
  43. package/lib/old/sdk-client.js +85 -0
  44. package/lib/old/to-register.d.ts +69 -0
  45. package/lib/old/to-register.js +55 -2
  46. package/lib/old/tools/authenticate.js +3 -7
  47. package/lib/old/tools/build.d.ts +6 -1
  48. package/lib/old/tools/build.js +11 -1
  49. package/lib/old/tools/get-provider-info.d.ts +16 -0
  50. package/lib/old/tools/get-provider-info.js +73 -0
  51. package/lib/old/tools/get-version-config.d.ts +17 -0
  52. package/lib/old/tools/get-version-config.js +47 -0
  53. package/lib/old/tools/publish.js +208 -2
  54. package/lib/old/tools/update-provider-metadata.d.ts +16 -0
  55. package/lib/old/tools/update-provider-metadata.js +61 -0
  56. package/lib/proof/attestor.d.ts +6 -3
  57. package/lib/proof/attestor.js +10 -1
  58. package/lib/proof/debug-response-dump.d.ts +4 -0
  59. package/lib/proof/debug-response-dump.js +55 -0
  60. package/lib/proof/patch-domhandler.d.ts +1 -0
  61. package/lib/proof/patch-domhandler.js +59 -0
  62. package/lib/proof/zk-files.js +7 -6
  63. package/lib/provider/dom-xpath.d.ts +17 -0
  64. package/lib/provider/dom-xpath.js +187 -0
  65. package/lib/provider/draft.d.ts +30 -2
  66. package/lib/provider/draft.js +117 -28
  67. package/lib/provider/index.d.ts +4 -0
  68. package/lib/provider/index.js +7 -0
  69. package/lib/provider/injection-doc.js +6 -0
  70. package/lib/provider/matcher.d.ts +17 -20
  71. package/lib/provider/matcher.js +394 -280
  72. package/lib/provider/schema.d.ts +7 -5
  73. package/lib/tools/registry.d.ts +14 -0
  74. package/lib/tools/registry.js +32 -0
  75. package/package.json +9 -1
  76. package/skill/SKILL.builder.md +42 -23
  77. package/skill/SKILL.old.md +6 -2
  78. package/skill/builder-mode.md +1 -1
  79. package/skill/injection-api.md +117 -0
  80. package/src/authoring/backend.ts +98 -0
  81. package/src/authoring/index.ts +20 -0
  82. package/src/authoring/tools.ts +273 -0
  83. package/src/authoring/util.ts +42 -0
  84. package/src/consts.ts +15 -2
  85. package/src/mcp/server.ts +11 -78
  86. package/src/mcp/start.ts +4 -1
  87. package/src/mcp/tools/agent/analyze.ts +3 -3
  88. package/src/mcp/tools/agent/build.ts +13 -21
  89. package/src/mcp/tools/agent/capture.ts +31 -115
  90. package/src/mcp/tools/agent/eval.ts +19 -155
  91. package/src/mcp/tools/agent/find.ts +33 -36
  92. package/src/mcp/tools/agent/proof.ts +87 -160
  93. package/src/mcp/tools/agent/propose.ts +93 -85
  94. package/src/mcp/tools/agent/replay.ts +32 -42
  95. package/src/mcp/tools/agent/session.ts +4 -5
  96. package/src/mcp/tools/agent/shared.ts +152 -0
  97. package/src/old/client.ts +60 -0
  98. package/src/old/login-cdp.ts +29 -23
  99. package/src/old/sdk-client.ts +105 -0
  100. package/src/old/to-register.ts +102 -6
  101. package/src/old/tools/authenticate.ts +3 -11
  102. package/src/old/tools/build.ts +12 -0
  103. package/src/old/tools/get-provider-info.ts +91 -0
  104. package/src/old/tools/get-version-config.ts +56 -0
  105. package/src/old/tools/publish.ts +264 -1
  106. package/src/old/tools/update-provider-metadata.ts +72 -0
  107. package/src/proof/attestor.ts +16 -4
  108. package/src/proof/debug-response-dump.ts +70 -0
  109. package/src/proof/patch-domhandler.ts +68 -0
  110. package/src/proof/zk-files.ts +7 -6
  111. package/src/provider/dom-xpath.ts +225 -0
  112. package/src/provider/draft.ts +168 -35
  113. package/src/provider/index.ts +8 -0
  114. package/src/provider/injection-doc.ts +6 -0
  115. package/src/provider/matcher.ts +477 -357
  116. package/src/provider/schema.ts +7 -5
  117. package/src/tools/registry.ts +92 -0
@@ -0,0 +1,273 @@
1
+ /**
2
+ * The authoring tools, authored ONCE — each directly with the shared
3
+ * `defineTool` primitive (no parallel tool spec). `authoringTools(resolve)`
4
+ * returns them as plain {@link RegisteredTool}s; the MCP (local Chrome +
5
+ * attestor) and the cloud loop (Popcorn + TEE) each pass their own backend
6
+ * resolver, so navigate/inspect/propose/replay/prove logic lives in one place.
7
+ *
8
+ * The resolver hands back the backend per call — the MCP keys it by `captureId`
9
+ * (multi-session), the cloud returns its one run backend. A host that needs
10
+ * extra input schema (the MCP's `captureId` + rich `run_proof` contract) MAPS
11
+ * these tools through `extendTool` at registration; the tools stay plain.
12
+ *
13
+ * Secret guarantee: captured cookie/auth values never enter a tool result. The
14
+ * draft (recipe + secrets) is held server-side; the model only sees an opaque
15
+ * `draftId` and the secret-free provider.
16
+ */
17
+ import { findTool } from '../mcp/tools/agent/find.ts'
18
+ import { proposeTool } from '../mcp/tools/agent/propose.ts'
19
+ import { replayTool } from '../mcp/tools/agent/replay.ts'
20
+ import type { CapturedRequest } from '../provider/schema.ts'
21
+ import { defineTool, type RegisteredTool } from '../tools/registry.ts'
22
+ import type { AuthoringBackend } from './backend.ts'
23
+ import { objectSchema, publicRequest, type ResolveBackend } from './util.ts'
24
+
25
+ const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))
26
+
27
+ interface RequestFilter {
28
+ method?: string
29
+ urlGlob?: string
30
+ statusMin?: number
31
+ statusMax?: number
32
+ contentType?: string
33
+ graphqlOp?: string
34
+ }
35
+
36
+ function matchesFilter(r: CapturedRequest, f: RequestFilter): boolean {
37
+ if(f.method && r.method !== f.method.toUpperCase()) {
38
+ return false
39
+ }
40
+
41
+ if(f.urlGlob && !globMatch(f.urlGlob, r.url)) {
42
+ return false
43
+ }
44
+
45
+ if(f.statusMin !== undefined && r.status < f.statusMin) {
46
+ return false
47
+ }
48
+
49
+ if(f.statusMax !== undefined && r.status > f.statusMax) {
50
+ return false
51
+ }
52
+
53
+ if(f.contentType && !r.contentType.includes(f.contentType)) {
54
+ return false
55
+ }
56
+
57
+ if(f.graphqlOp && r.graphqlOp !== f.graphqlOp) {
58
+ return false
59
+ }
60
+
61
+ return true
62
+ }
63
+
64
+ function globMatch(pattern: string, str: string): boolean {
65
+ const re = new RegExp(
66
+ '^'
67
+ + pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')
68
+ + '$',
69
+ )
70
+ return re.test(str)
71
+ }
72
+
73
+ interface EvalResult {
74
+ result?: { value?: unknown }
75
+ exceptionDetails?: { exception?: { description?: string }, text?: string }
76
+ }
77
+
78
+ async function evalInPage(
79
+ backend: AuthoringBackend,
80
+ expression: string,
81
+ ): Promise<{ value?: unknown, error?: string }> {
82
+ const res = (await backend.sendCdp('Runtime.evaluate', {
83
+ expression, returnByValue: true, awaitPromise: true,
84
+ })) as EvalResult
85
+ if(res.exceptionDetails) {
86
+ return {
87
+ error: res.exceptionDetails.exception?.description
88
+ ?? res.exceptionDetails.text
89
+ ?? 'evaluation error',
90
+ }
91
+ }
92
+
93
+ return { value: res.result?.value }
94
+ }
95
+
96
+ /**
97
+ * The authoring tools — navigate/inspect authored inline here; find/propose/
98
+ * replay live in their own files ({@link findTool}/{@link proposeTool}/
99
+ * {@link replayTool}) and are composed in below. Each is a plain `defineTool`
100
+ * primitive, so there is one canonical `RegisteredTool` per tool and no
101
+ * parallel tool shape. Every handler resolves its session backend via
102
+ * `resolveBackend(args)`: the cloud passes `() => backend` (one backend per
103
+ * run); the MCP passes an `args.captureId`-keyed resolver. Hosts that need
104
+ * extra input schema (the MCP's `captureId` session handle + the
105
+ * credential-aware `run_proof` contract) MAP these tools through `extendTool`
106
+ * (see mcp/tools/agent/shared.ts) — the tools themselves stay plain, and args
107
+ * parse loosely so the extra fields still reach the resolver/handler.
108
+ */
109
+ export function authoringTools(
110
+ resolveBackend: ResolveBackend,
111
+ ): RegisteredTool[] {
112
+ return [
113
+ defineTool<Record<string, unknown>>(
114
+ {
115
+ name: 'navigate',
116
+ description: 'Navigate the browser tab to a URL, waiting for load.',
117
+ inputSchema: objectSchema(
118
+ { url: { type: 'string' }, timeoutMs: { type: 'number' } },
119
+ ['url'],
120
+ ),
121
+ },
122
+ async(args) => {
123
+ const backend = resolveBackend(args)
124
+ const timeoutMs = Number(args.timeoutMs) || 30_000
125
+ await backend.sendCdp('Page.enable')
126
+ // Start listening BEFORE navigating so the load event isn't missed.
127
+ const load = backend.waitForEvent('Page.loadEventFired', timeoutMs)
128
+ const nav = (await backend.sendCdp(
129
+ 'Page.navigate', { url: String(args.url) },
130
+ )) as { errorText?: string }
131
+ if(nav.errorText) {
132
+ return { ok: false, error: nav.errorText }
133
+ }
134
+
135
+ const loadStatus = await load
136
+ const final = await evalInPage(backend, 'location.href')
137
+ return { ok: true, loadStatus, finalUrl: final.value }
138
+ },
139
+ ),
140
+ defineTool<Record<string, unknown>>(
141
+ {
142
+ name: 'eval_in_page',
143
+ description:
144
+ 'Evaluate a JavaScript expression in the page and return its value '
145
+ + '(use to read the displayed value, check login state, etc.).',
146
+ inputSchema: objectSchema(
147
+ { expression: { type: 'string' } }, ['expression'],
148
+ ),
149
+ },
150
+ (args) => evalInPage(resolveBackend(args), String(args.expression)),
151
+ ),
152
+ defineTool<Record<string, unknown>>(
153
+ {
154
+ name: 'wait_for_page',
155
+ description:
156
+ 'Poll a JavaScript expression until it is truthy or the timeout '
157
+ + 'elapses (e.g. wait for the user to finish logging in).',
158
+ inputSchema: objectSchema(
159
+ { expression: { type: 'string' }, timeoutMs: { type: 'number' } },
160
+ ['expression'],
161
+ ),
162
+ },
163
+ async(args) => {
164
+ const backend = resolveBackend(args)
165
+ const expr = String(args.expression)
166
+ const deadline = Date.now() + (Number(args.timeoutMs) || 180_000)
167
+ while(Date.now() < deadline) {
168
+ const { value } = await evalInPage(backend, expr)
169
+ if(value) {
170
+ return { ready: true }
171
+ }
172
+
173
+ await sleep(1000)
174
+ }
175
+
176
+ return { ready: false }
177
+ },
178
+ ),
179
+ defineTool<Record<string, unknown>>(
180
+ {
181
+ name: 'get_cookies',
182
+ description:
183
+ 'List the NAMES of cookies currently set for the page (values are '
184
+ + 'NEVER returned). The most reliable, language- and DOM-independent '
185
+ + 'login signal: after the user logs in, an auth/session cookie '
186
+ + 'appears (e.g. session/sid/token/auth/jwt/_user/logged_in). '
187
+ + 'Includes httpOnly cookies the page JS cannot see.',
188
+ inputSchema: objectSchema({}),
189
+ },
190
+ async(args) => {
191
+ const backend = resolveBackend(args)
192
+ const res = (await backend.sendCdp('Network.getCookies')) as {
193
+ cookies?: {
194
+ name: string
195
+ domain?: string
196
+ session?: boolean
197
+ httpOnly?: boolean
198
+ }[]
199
+ }
200
+ return (res.cookies ?? []).map((c) => ({
201
+ name: c.name,
202
+ ...(c.domain ? { domain: c.domain } : {}),
203
+ ...(c.session ? { session: true } : {}),
204
+ ...(c.httpOnly ? { httpOnly: true } : {}),
205
+ }))
206
+ },
207
+ ),
208
+ defineTool<Record<string, unknown>>(
209
+ {
210
+ name: 'list_requests',
211
+ description:
212
+ 'List captured network requests (method/url/status/size), newest '
213
+ + 'last. Optionally filter by method / urlGlob / status range / '
214
+ + 'contentType / graphqlOp. Metadata only — secrets never included.',
215
+ inputSchema: objectSchema({
216
+ filter: {
217
+ type: 'object',
218
+ properties: {
219
+ method: { type: 'string' },
220
+ urlGlob: { type: 'string' },
221
+ statusMin: { type: 'number' },
222
+ statusMax: { type: 'number' },
223
+ contentType: { type: 'string' },
224
+ graphqlOp: { type: 'string' },
225
+ },
226
+ },
227
+ }),
228
+ },
229
+ async(args) => {
230
+ const backend = resolveBackend(args)
231
+ const filter = (args.filter ?? {}) as RequestFilter
232
+ return [...backend.capture.requests.values()]
233
+ .filter((r) => matchesFilter(r, filter))
234
+ .map(publicRequest)
235
+ },
236
+ ),
237
+ findTool(resolveBackend),
238
+ proposeTool(resolveBackend),
239
+ replayTool(resolveBackend),
240
+ defineTool<Record<string, unknown>>(
241
+ {
242
+ name: 'run_proof',
243
+ description:
244
+ 'Prove the drafted recipe (zkTLS). Only call after replay_request '
245
+ + 'looks right. Returns the proof result.',
246
+ inputSchema: objectSchema({ draftId: { type: 'string' } }, ['draftId']),
247
+ },
248
+ // The backend shapes its own result: the cloud returns a lean,
249
+ // secret-free summary (and records the proven recipe for publish); the
250
+ // MCP persists the claim + returns the rich developer view. The MCP adds
251
+ // `ownerAddress`/`attestorUrl` to the schema via `extendTool`; the
252
+ // handler reads them from args (loose parse keeps them).
253
+ async(args) => {
254
+ const backend = resolveBackend(args)
255
+ const draft = backend.drafts.get(String(args.draftId))
256
+ if(!draft) {
257
+ throw new Error(`no draft ${String(args.draftId)}`)
258
+ }
259
+
260
+ const opts: { ownerAddress?: string, attestorUrl?: string } = {}
261
+ if(typeof args.ownerAddress === 'string') {
262
+ opts.ownerAddress = args.ownerAddress
263
+ }
264
+
265
+ if(typeof args.attestorUrl === 'string') {
266
+ opts.attestorUrl = args.attestorUrl
267
+ }
268
+
269
+ return backend.prove(draft, opts)
270
+ },
271
+ ),
272
+ ]
273
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Small shared helpers for the authoring tools (see tools.ts). Kept separate so
3
+ * the per-tool files (find.ts, propose.ts, replay.ts) and the inline tools in
4
+ * tools.ts share one copy without a circular import.
5
+ */
6
+ import type { JSONSchema } from 'zod/v4/core'
7
+ import type { CapturedRequest } from '../provider/schema.ts'
8
+ import type { AuthoringBackend } from './backend.ts'
9
+
10
+ /**
11
+ * Resolves the session backend for a tool call. The MCP keys it by
12
+ * `args.captureId` (multi-session); the cloud loop returns its one run backend.
13
+ * Each authoring tool closes over this so the same tool works for both hosts.
14
+ */
15
+ export type ResolveBackend =
16
+ (args: Record<string, unknown>) => AuthoringBackend
17
+
18
+ /** Build an `object` JSON Schema for a tool input — a small literal shortener,
19
+ * not a tool abstraction. */
20
+ export function objectSchema(
21
+ properties: Record<string, unknown>,
22
+ required?: string[],
23
+ ): JSONSchema.ObjectSchema {
24
+ return {
25
+ type: 'object',
26
+ properties,
27
+ ...(required?.length ? { required } : {}),
28
+ } as unknown as JSONSchema.ObjectSchema
29
+ }
30
+
31
+ /** Secret-free projection of a captured request for tool output. */
32
+ export function publicRequest(r: CapturedRequest) {
33
+ return {
34
+ requestId: r.requestId,
35
+ method: r.method,
36
+ url: r.url,
37
+ status: r.status,
38
+ contentType: r.contentType,
39
+ size: r.size,
40
+ ...(r.graphqlOp ? { graphqlOp: r.graphqlOp } : {}),
41
+ }
42
+ }
package/src/consts.ts CHANGED
@@ -19,6 +19,7 @@ import { DEFAULT_BASE_URL } from '@reclaimprotocol/client'
19
19
  import { homedir } from 'node:os'
20
20
  import { join } from 'node:path'
21
21
  import { DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL } from './old/client.ts'
22
+ import { DEFAULT_SDK_API_URL } from './old/sdk-client.ts'
22
23
 
23
24
  // ---------------------------------------------------------------------------
24
25
  // Default / fallback literals (used only as env fallbacks).
@@ -30,9 +31,14 @@ export const FALLBACK_ATTESTOR_URL = 'wss://attestor.reclaimprotocol.org:444/ws'
30
31
  /** The devtools dashboard login page — fallback for `RECLAIM_OLD_LOGIN_URL`. */
31
32
  export const DEFAULT_LOGIN_URL = 'https://dev.reclaimprotocol.org'
32
33
 
33
- // Re-export the two non-env-fallback defaults so callers can keep importing
34
+ // Re-export the non-env-fallback defaults so callers can keep importing
34
35
  // their URL defaults from a single place if they wish.
35
- export { DEFAULT_BASE_URL, DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL }
36
+ export {
37
+ DEFAULT_BASE_URL,
38
+ DEFAULT_OLD_API_URL,
39
+ DEFAULT_OLD_LOGS_URL,
40
+ DEFAULT_SDK_API_URL,
41
+ }
36
42
 
37
43
  // ---------------------------------------------------------------------------
38
44
  // Logging.
@@ -101,6 +107,13 @@ export function oldLogsUrl(): string {
101
107
  return process.env.RECLAIM_OLD_LOGS_URL || DEFAULT_OLD_LOGS_URL
102
108
  }
103
109
 
110
+ /** Public production SDK-backend base URL — the service the verification
111
+ * SDK/attestor itself reads a provider's recipe from at runtime. A
112
+ * DIFFERENT host from the old-devtools dashboard backend (`oldApiUrl`). */
113
+ export function sdkApiUrl(): string {
114
+ return process.env.RECLAIM_SDK_API_URL || DEFAULT_SDK_API_URL
115
+ }
116
+
104
117
  /** Builder backend base URL. Shared by mcp/start and the verification tool. */
105
118
  export function apiUrl(): string {
106
119
  return process.env.RECLAIM_API_URL || DEFAULT_BASE_URL
package/src/mcp/server.ts CHANGED
@@ -1,65 +1,16 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import type { JSONSchema } from 'zod/v4/core'
2
+ import { type RegisteredTool } from '../tools/registry.ts'
3
3
  import { toZod } from './api/to-zod.ts'
4
4
  import { remapErrorAsResponse } from './remap_errors.ts'
5
5
 
6
- export interface ToolDefinition {
7
- name: string
8
- description: string
9
- inputSchema: JSONSchema.ObjectSchema
10
- }
11
-
12
- /**
13
- * Passed to every tool handler alongside its parsed args. Long-running,
14
- * human-in-the-loop tools (e.g. a browser login flow that waits for someone
15
- * to sign in) should call `reportProgress` every few seconds while waiting.
16
- * MCP clients that attach a progress token to their request (most desktop
17
- * clients do) treat an incoming `notifications/progress` as proof the call
18
- * is still alive and reset their own request timeout instead of giving up —
19
- * the SDK's client-side default is 60s, far shorter than a human can be
20
- * expected to take to log in. A no-op when the client didn't request
21
- * progress tracking for this call.
22
- */
23
- export interface ToolContext {
24
- reportProgress: (message: string) => Promise<void>
25
- }
26
-
27
- const NOOP_CONTEXT: ToolContext = { reportProgress: async() => {} }
28
-
29
- export interface RegisteredTool {
30
- definition: ToolDefinition
31
- /**
32
- * @param args Tool arguments
33
- * @param ctx Progress-reporting context (see {@link ToolContext}).
34
- * Optional so direct/test callers can omit it.
35
- * @returns A JSON stringifiable content to be returned to the llm
36
- * @throws Any error encountered during tool execution will be caught and
37
- * returned as a response using
38
- * [remapErrorAsResponse](./src/mcp/remap_errors.ts).
39
- */
40
- handler: (args: unknown, ctx?: ToolContext) => Promise<unknown>
41
- }
42
-
43
- /**
44
- * Define a tool with a single inputSchema (JSON Schema) used as the
45
- * source of truth. The handler receives args already parsed and
46
- * type-narrowed via a Zod schema derived from that same JSON Schema —
47
- * so the description sent to the MCP client and the validation applied
48
- * to inbound args cannot drift.
49
- */
50
- export function defineTool<TArgs>(
51
- definition: ToolDefinition,
52
- handler: (args: TArgs, ctx: ToolContext) => Promise<unknown>,
53
- ): RegisteredTool {
54
- const schema = toZod(definition.inputSchema)
55
- return {
56
- definition,
57
- handler: async(raw, ctx = NOOP_CONTEXT) => {
58
- const args = schema.parse(raw) as TArgs
59
- return await handler(args, ctx)
60
- },
61
- }
62
- }
6
+ // The tool primitive lives in `../tools/registry.ts` (SDK-free, so the cloud
7
+ // loop can use it too). Re-exported here so the MCP-side tool files can keep
8
+ // importing `defineTool`/`RegisteredTool` from the server module.
9
+ export {
10
+ defineTool,
11
+ type RegisteredTool,
12
+ type ToolDefinition,
13
+ } from '../tools/registry.ts'
63
14
 
64
15
  /**
65
16
  * Registers a list of RegisteredTool entries on a high-level McpServer.
@@ -74,31 +25,13 @@ export function registerToolList(server: McpServer, tools: RegisteredTool[]) {
74
25
  description: tool.definition.description,
75
26
  inputSchema: toZod(tool.definition.inputSchema),
76
27
  },
77
- async(args, extra) => {
28
+ async(args) => {
78
29
  try {
79
- const progressToken = extra?._meta?.progressToken
80
- let progress = 0
81
- const ctx: ToolContext = {
82
- reportProgress: async(message) => {
83
- if(progressToken === undefined) {
84
- return
85
- }
86
-
87
- progress += 1
88
- await extra.sendNotification({
89
- method: 'notifications/progress',
90
- params: { progressToken, progress, message },
91
- }).catch(() => {})
92
- },
93
- }
94
-
95
30
  return {
96
31
  content: [
97
32
  {
98
33
  type: 'text' as const,
99
- text: JSON.stringify(
100
- await tool.handler(args, ctx), null, 2,
101
- ),
34
+ text: JSON.stringify(await tool.handler(args), null, 2),
102
35
  },
103
36
  ],
104
37
  }
package/src/mcp/start.ts CHANGED
@@ -7,11 +7,13 @@ import {
7
7
  apiUrl,
8
8
  oldApiUrl,
9
9
  oldLogsUrl,
10
+ sdkApiUrl,
10
11
  useOldDevtools,
11
12
  } from '../consts.ts'
12
13
  import { ReclaimOldClient } from '../old/client.ts'
13
14
  import { resolveInitialIdentity } from '../old/identity.ts'
14
15
  import { OLD_MODE_INSTRUCTIONS } from '../old/instructions.ts'
16
+ import { ReclaimSdkClient } from '../old/sdk-client.ts'
15
17
  import { buildOldBackendTools } from '../old/tools/build.ts'
16
18
  import { NAME, VERSION } from '../pkg.ts'
17
19
  import { registerTools } from './api/tools.ts'
@@ -53,9 +55,10 @@ export async function startMcpServer() {
53
55
  logsBaseUrl: oldLogsUrl(),
54
56
  identity: resolveInitialIdentity(),
55
57
  })
58
+ const sdkClient = new ReclaimSdkClient({ baseUrl: sdkApiUrl() })
56
59
 
57
60
  registerToolList(server, buildLocalCredentialTools())
58
- registerToolList(server, buildOldBackendTools(oldClient))
61
+ registerToolList(server, buildOldBackendTools(oldClient, sdkClient))
59
62
  if(agentToolsEnabled) {
60
63
  // No builder client → no builder publish tool (old mode has its own).
61
64
  registerToolList(server, buildAgentTools())
@@ -18,12 +18,12 @@ export function analyzeTool(captures: CaptureStore): RegisteredTool {
18
18
  },
19
19
  },
20
20
  async({ captureId, requestId }) => {
21
- const session = captures.sessions.get(captureId)
22
- if(!session) {
21
+ const entry = captures.sessions.get(captureId)
22
+ if(!entry) {
23
23
  throw new Error(`no capture ${captureId}`)
24
24
  }
25
25
 
26
- const r = session.requests.get(requestId)
26
+ const r = entry.session.requests.get(requestId)
27
27
  if(!r) {
28
28
  throw new Error(`no request ${requestId} in capture ${captureId}`)
29
29
  }
@@ -4,8 +4,10 @@
4
4
  * `@modelcontextprotocol/sdk` `McpServer`. Folds the agent's tools
5
5
  * alongside the auto-generated API tools.
6
6
  *
7
- * Shared state — Chrome attach state, capture store, secret store — is
8
- * built once per server and threaded through every tool factory.
7
+ * Shared state — Chrome attach state + capture store — is built once per
8
+ * server and threaded through every tool factory. Per-capture drafts (and the
9
+ * secrets they hold) live inside each capture entry, so a capture IS the
10
+ * session for the shared authoring tools.
9
11
  */
10
12
 
11
13
  import type { ReclaimClient } from '@reclaimprotocol/client'
@@ -15,30 +17,21 @@ import { type RegisteredTool } from '../../server.ts'
15
17
  import { analyzeTool } from './analyze.ts'
16
18
  import { type AttachState, attachTool } from './attach.ts'
17
19
  import { type CaptureStore, captureTools } from './capture.ts'
18
- import { browserTools } from './eval.ts'
19
- import { findTool } from './find.ts'
20
+ import { listTabsTool } from './eval.ts'
20
21
  import { howItWorksTool } from './how-it-works.ts'
21
- import { proofTool } from './proof.ts'
22
- import {
23
- makeSecretStore,
24
- proposeTool,
25
- type SecretStore,
26
- } from './propose.ts'
27
22
  import { createVersionFromCaptureTool } from './publish.ts'
28
- import { replayTool } from './replay.ts'
29
23
  import { sessionTools } from './session.ts'
24
+ import { sharedAuthoringTools } from './shared.ts'
30
25
 
31
26
  export interface AgentState {
32
27
  attach: { current?: AttachState, config: ConfigStore }
33
28
  captures: CaptureStore
34
- secrets: SecretStore
35
29
  }
36
30
 
37
31
  function buildAgentState(): AgentState {
38
32
  return {
39
33
  attach: { config: new ConfigStore() },
40
34
  captures: { sessions: new Map() },
41
- secrets: makeSecretStore(),
42
35
  }
43
36
  }
44
37
 
@@ -57,24 +50,23 @@ export function buildAgentTools(
57
50
  // client. No `client` ⇒ old-devtools mode (see `mcp/start.ts`).
58
51
  tools.push(howItWorksTool(!client))
59
52
  tools.push(attachTool(state.attach))
53
+ tools.push(listTabsTool(state.attach))
60
54
  for(const t of captureTools(state.attach, state.captures)) {
61
55
  tools.push(t)
62
56
  }
63
57
 
64
- tools.push(findTool(state.captures))
65
- for(const t of browserTools(state.attach)) {
58
+ for(const t of sessionTools(state.attach, state.captures)) {
66
59
  tools.push(t)
67
60
  }
68
61
 
69
- for(const t of sessionTools(state.attach, state.captures, state.secrets)) {
62
+ tools.push(analyzeTool(state.captures))
63
+ // navigate / eval_in_page / wait_for_page / get_cookies / list_requests /
64
+ // find_requests_containing / propose_provider / replay_request / run_proof —
65
+ // shared with the cloud loop, bound to each capture's local backend.
66
+ for(const t of sharedAuthoringTools(state.captures)) {
70
67
  tools.push(t)
71
68
  }
72
69
 
73
- tools.push(analyzeTool(state.captures))
74
- tools.push(proposeTool(state.captures, state.secrets))
75
- tools.push(replayTool(state.secrets))
76
- tools.push(proofTool(state.secrets))
77
-
78
70
  if(client) {
79
71
  tools.push(createVersionFromCaptureTool(client))
80
72
  }