@reclaimprotocol/agent 0.1.3 → 0.1.5

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 (58) hide show
  1. package/README.md +11 -42
  2. package/lib/authoring/backend.d.ts +95 -0
  3. package/lib/authoring/backend.js +24 -0
  4. package/lib/authoring/index.d.ts +4 -0
  5. package/lib/authoring/index.js +5 -0
  6. package/lib/authoring/tools.d.ts +9 -0
  7. package/lib/authoring/tools.js +378 -0
  8. package/lib/cdp/capture-engine.d.ts +7 -0
  9. package/lib/cdp/capture-engine.js +78 -0
  10. package/lib/cdp/capture.d.ts +1 -1
  11. package/lib/cdp/capture.js +1 -1
  12. package/lib/consts.d.ts +4 -2
  13. package/lib/consts.js +6 -2
  14. package/lib/mcp/api/operations.gen.js +46 -18
  15. package/lib/mcp/instructions.d.ts +2 -0
  16. package/lib/mcp/instructions.js +16 -0
  17. package/lib/mcp/server.d.ts +18 -2
  18. package/lib/mcp/server.js +19 -4
  19. package/lib/mcp/start.js +2 -1
  20. package/lib/mcp/tools/agent/build.js +4 -0
  21. package/lib/mcp/tools/agent/how-it-works.d.ts +11 -0
  22. package/lib/mcp/tools/agent/how-it-works.js +24 -0
  23. package/lib/mcp/tools/agent/publish.js +2 -2
  24. package/lib/mcp/tools/agent/shared.d.ts +6 -0
  25. package/lib/mcp/tools/agent/shared.js +115 -0
  26. package/lib/old/client.d.ts +10 -0
  27. package/lib/old/client.js +27 -0
  28. package/lib/old/login-cdp.d.ts +5 -0
  29. package/lib/old/login-cdp.js +12 -1
  30. package/lib/old/tools/authenticate.js +7 -3
  31. package/lib/old/tools/build.d.ts +2 -2
  32. package/lib/old/tools/build.js +4 -2
  33. package/lib/old/tools/session-analytics-logs.d.ts +9 -0
  34. package/lib/old/tools/session-analytics-logs.js +25 -0
  35. package/lib/proof/proof-core.d.ts +1 -1
  36. package/lib/proof/proof-core.js +1 -1
  37. package/lib/tools/registry.d.ts +36 -0
  38. package/lib/tools/registry.js +17 -0
  39. package/package.json +2 -3
  40. package/skill/{SKILL.md → SKILL.builder.md} +16 -45
  41. package/skill/SKILL.old.md +225 -0
  42. package/skill/old-mode.md +2 -0
  43. package/src/cdp/capture-engine.ts +89 -0
  44. package/src/cdp/capture.ts +5 -1
  45. package/src/consts.ts +7 -2
  46. package/src/mcp/api/operations.gen.ts +46 -18
  47. package/src/mcp/instructions.ts +18 -0
  48. package/src/mcp/server.ts +43 -6
  49. package/src/mcp/start.ts +2 -0
  50. package/src/mcp/tools/agent/build.ts +4 -0
  51. package/src/mcp/tools/agent/how-it-works.ts +32 -0
  52. package/src/mcp/tools/agent/publish.ts +2 -2
  53. package/src/old/client.ts +32 -0
  54. package/src/old/login-cdp.ts +20 -1
  55. package/src/old/tools/authenticate.ts +11 -3
  56. package/src/old/tools/build.ts +4 -2
  57. package/src/old/tools/session-analytics-logs.ts +33 -0
  58. package/src/proof/proof-core.ts +1 -1
@@ -13,3 +13,21 @@ export const BUILDER_MODE_INSTRUCTIONS = readFileSync(
13
13
  fileURLToPath(new URL('../../skill/builder-mode.md', import.meta.url)),
14
14
  'utf8',
15
15
  ).trim()
16
+
17
+ /**
18
+ * The full provider-authoring guide — the deep capture→prove→publish workflow,
19
+ * decision frameworks, and troubleshooting. Served on demand by the
20
+ * `how_it_works` MCP tool so every client (not just Claude Code, which
21
+ * auto-loads skills) can pull it. There's a guide PER MODE so an agent is never
22
+ * shown auth/publish tools that don't exist in its backend: the server picks
23
+ * one by `oldMode` (see `mcp/start.ts`). Same `../../skill/` path resolution as
24
+ * the instructions above; any leading frontmatter is stripped defensively.
25
+ */
26
+ function readGuide(file: string): string {
27
+ return readFileSync(fileURLToPath(new URL(file, import.meta.url)), 'utf8')
28
+ .replace(/^---\n[\s\S]*?\n---\n/, '')
29
+ .trim()
30
+ }
31
+
32
+ export const PROVIDER_GUIDE_BUILDER = readGuide('../../skill/SKILL.builder.md')
33
+ export const PROVIDER_GUIDE_OLD = readGuide('../../skill/SKILL.old.md')
package/src/mcp/server.ts CHANGED
@@ -9,16 +9,35 @@ export interface ToolDefinition {
9
9
  inputSchema: JSONSchema.ObjectSchema
10
10
  }
11
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
+
12
29
  export interface RegisteredTool {
13
30
  definition: ToolDefinition
14
31
  /**
15
32
  * @param args Tool arguments
33
+ * @param ctx Progress-reporting context (see {@link ToolContext}).
34
+ * Optional so direct/test callers can omit it.
16
35
  * @returns A JSON stringifiable content to be returned to the llm
17
36
  * @throws Any error encountered during tool execution will be caught and
18
37
  * returned as a response using
19
38
  * [remapErrorAsResponse](./src/mcp/remap_errors.ts).
20
39
  */
21
- handler: (args: unknown) => Promise<unknown>
40
+ handler: (args: unknown, ctx?: ToolContext) => Promise<unknown>
22
41
  }
23
42
 
24
43
  /**
@@ -30,14 +49,14 @@ export interface RegisteredTool {
30
49
  */
31
50
  export function defineTool<TArgs>(
32
51
  definition: ToolDefinition,
33
- handler: (args: TArgs) => Promise<unknown>,
52
+ handler: (args: TArgs, ctx: ToolContext) => Promise<unknown>,
34
53
  ): RegisteredTool {
35
54
  const schema = toZod(definition.inputSchema)
36
55
  return {
37
56
  definition,
38
- handler: async(raw) => {
57
+ handler: async(raw, ctx = NOOP_CONTEXT) => {
39
58
  const args = schema.parse(raw) as TArgs
40
- return await handler(args)
59
+ return await handler(args, ctx)
41
60
  },
42
61
  }
43
62
  }
@@ -55,13 +74,31 @@ export function registerToolList(server: McpServer, tools: RegisteredTool[]) {
55
74
  description: tool.definition.description,
56
75
  inputSchema: toZod(tool.definition.inputSchema),
57
76
  },
58
- async(args) => {
77
+ async(args, extra) => {
59
78
  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
+
60
95
  return {
61
96
  content: [
62
97
  {
63
98
  type: 'text' as const,
64
- text: JSON.stringify(await tool.handler(args), null, 2),
99
+ text: JSON.stringify(
100
+ await tool.handler(args, ctx), null, 2,
101
+ ),
65
102
  },
66
103
  ],
67
104
  }
package/src/mcp/start.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  apiToken,
7
7
  apiUrl,
8
8
  oldApiUrl,
9
+ oldLogsUrl,
9
10
  useOldDevtools,
10
11
  } from '../consts.ts'
11
12
  import { ReclaimOldClient } from '../old/client.ts'
@@ -49,6 +50,7 @@ export async function startMcpServer() {
49
50
  // get_me_providers instead.
50
51
  const oldClient = new ReclaimOldClient({
51
52
  baseUrl: oldApiUrl(),
53
+ logsBaseUrl: oldLogsUrl(),
52
54
  identity: resolveInitialIdentity(),
53
55
  })
54
56
 
@@ -17,6 +17,7 @@ import { type AttachState, attachTool } from './attach.ts'
17
17
  import { type CaptureStore, captureTools } from './capture.ts'
18
18
  import { browserTools } from './eval.ts'
19
19
  import { findTool } from './find.ts'
20
+ import { howItWorksTool } from './how-it-works.ts'
20
21
  import { proofTool } from './proof.ts'
21
22
  import {
22
23
  makeSecretStore,
@@ -52,6 +53,9 @@ export function buildAgentTools(
52
53
  const state = buildAgentState()
53
54
  ensureAttestorCrypto()
54
55
  const tools: RegisteredTool[] = []
56
+ // Stateless docs tool — serve the mode-specific authoring guide to any
57
+ // client. No `client` ⇒ old-devtools mode (see `mcp/start.ts`).
58
+ tools.push(howItWorksTool(!client))
55
59
  tools.push(attachTool(state.attach))
56
60
  for(const t of captureTools(state.attach, state.captures)) {
57
61
  tools.push(t)
@@ -0,0 +1,32 @@
1
+ import {
2
+ PROVIDER_GUIDE_BUILDER,
3
+ PROVIDER_GUIDE_OLD,
4
+ } from '../../instructions.ts'
5
+ import { defineTool, type RegisteredTool } from '../../server.ts'
6
+
7
+ /**
8
+ * Serves the full provider-authoring guide on demand. The MCP server's
9
+ * always-on `instructions` are a terse tool-flow reference; this tool returns
10
+ * the deep version (workflow, decision frameworks, gotchas, "never do" rules)
11
+ * so any MCP client — not just Claude Code, which alone auto-loads skills — can
12
+ * pull it. `oldMode` selects the backend-specific guide so the agent is never
13
+ * told to call auth/publish tools that aren't registered in its mode.
14
+ * Stateless: callable before `attach_chrome`.
15
+ */
16
+ export function howItWorksTool(oldMode: boolean): RegisteredTool {
17
+ const guide = oldMode ? PROVIDER_GUIDE_OLD : PROVIDER_GUIDE_BUILDER
18
+ return defineTool<Record<string, never>>(
19
+ {
20
+ name: 'how_it_works',
21
+ description:
22
+ 'Return the full Reclaim provider-authoring guide for THIS server: '
23
+ + 'the capture → draft → replay → prove → publish workflow, decision '
24
+ + 'frameworks (login detection, OPRF privacy, context vs bare params), '
25
+ + 'multi-request handling, troubleshooting, and what never to do. '
26
+ + 'Call this BEFORE building a provider unless you are already '
27
+ + 'fluent in the flow.',
28
+ inputSchema: { type: 'object', properties: {} },
29
+ },
30
+ async() => ({ guide }),
31
+ )
32
+ }
@@ -129,7 +129,7 @@ export function createVersionFromCaptureTool(
129
129
  {
130
130
  name: 'create_provider_version_from_capture',
131
131
  description:
132
- 'Persist drafted ReclaimProvider(s) as a verification version. ' +
132
+ 'Persist drafted ReclaimProvider(s) as a provider version. ' +
133
133
  'Call this after run_proof succeeds, passing the `provider` ' +
134
134
  'object(s) from propose_provider VERBATIM. This tool builds the ' +
135
135
  'body (the requests[] array, webSettings, etc.) for you — do NOT ' +
@@ -160,7 +160,7 @@ export function createVersionFromCaptureTool(
160
160
  properties: {
161
161
  providerId: {
162
162
  type: 'string',
163
- description: 'UUID of the verification to publish the version on.',
163
+ description: 'UUID of the provider to publish the version on.',
164
164
  },
165
165
  provider: PROVIDER_SCHEMA,
166
166
  providers: {
package/src/old/client.ts CHANGED
@@ -6,6 +6,10 @@ import { NAME, VERSION } from '../pkg.ts'
6
6
  * `RECLAIM_OLD_API_URL`. */
7
7
  export const DEFAULT_OLD_API_URL = 'https://devapi.reclaimprotocol.org'
8
8
 
9
+ /** Default origin of the OLD-devtools analytics-logs service — a SEPARATE host
10
+ * from the devtools API. Override with `RECLAIM_OLD_LOGS_URL`. */
11
+ export const DEFAULT_OLD_LOGS_URL = 'https://logs.reclaimprotocol.org'
12
+
9
13
  /**
10
14
  * Externally-supplied caller identity. We do NOT mint these — the dev
11
15
  * provides one:
@@ -63,15 +67,19 @@ interface RequestOpts {
63
67
  */
64
68
  export class ReclaimOldClient {
65
69
  readonly baseUrl: string
70
+ readonly logsBaseUrl: string
66
71
  #identity?: OldIdentity
67
72
  #fetch: typeof fetch
68
73
 
69
74
  constructor(opts: {
70
75
  baseUrl?: string
76
+ logsBaseUrl?: string
71
77
  identity?: OldIdentity
72
78
  fetch?: typeof fetch
73
79
  } = {}) {
74
80
  this.baseUrl = (opts.baseUrl || DEFAULT_OLD_API_URL).replace(/\/+$/, '')
81
+ this.logsBaseUrl = (opts.logsBaseUrl || DEFAULT_OLD_LOGS_URL)
82
+ .replace(/\/+$/, '')
75
83
  this.#identity = opts.identity
76
84
  this.#fetch = opts.fetch ?? fetch
77
85
  }
@@ -113,6 +121,30 @@ export class ReclaimOldClient {
113
121
  )
114
122
  }
115
123
 
124
+ /** Analytics logs for a verification session, from the logs service (a
125
+ * DIFFERENT host than the devtools API — hence its own fetch, not
126
+ * `#request`). Attaches the caller identity when one is set; queried
127
+ * anonymously otherwise. */
128
+ async getSessionAnalyticsLogs(sessionId: string): Promise<unknown> {
129
+ const url = `${this.logsBaseUrl}/api/analytics-logs/session/`
130
+ + encodeURIComponent(sessionId)
131
+ const headers: Record<string, string> = {
132
+ Accept: 'application/json',
133
+ 'User-Agent': `${NAME}/${VERSION}`,
134
+ }
135
+ if(this.#identity) {
136
+ headers[this.#authHeaderName()] = this.#authHeaderValue()
137
+ }
138
+
139
+ const res = await this.#fetch(url, { method: 'GET', headers })
140
+ const json = await this.#parseBody(res)
141
+ if(!res.ok) {
142
+ throw this.#asBoom(res, json)
143
+ }
144
+
145
+ return json
146
+ }
147
+
116
148
  getMyProviders(query: MyProvidersQuery = {}): Promise<unknown> {
117
149
  // Every one of these query params is REQUIRED by the backend
118
150
  // validator (missing/invalid → 400), so default them all here.
@@ -79,11 +79,18 @@ export async function captureFirebaseTokenViaCdp(opts: {
79
79
  loginUrl?: string
80
80
  timeoutMs?: number
81
81
  pollMs?: number
82
+ /** Called every ~10s while waiting for the human to finish signing in, so
83
+ * MCP clients that track progress on this call can reset their own
84
+ * request timeout instead of giving up on a login that's still in
85
+ * flight (see `ToolContext` in `mcp/server.ts`). */
86
+ onProgress?: (message: string) => Promise<void>
82
87
  }): Promise<CdpLoginResult> {
83
88
  const loginUrl =
84
89
  opts.loginUrl || oldLoginUrl() || DEFAULT_LOGIN_URL
85
90
  const timeoutMs = opts.timeoutMs ?? 180_000
86
91
  const pollMs = opts.pollMs ?? 1000
92
+ const onProgress = opts.onProgress ?? (async() => {})
93
+ const progressIntervalMs = 10_000
87
94
 
88
95
  const cfg = new ConfigStore().read()?.browser_agent
89
96
  const mode = cfg?.attachMode ?? 'dedicated'
@@ -114,7 +121,9 @@ export async function captureFirebaseTokenViaCdp(opts: {
114
121
  try {
115
122
  const session = await handles.attachToTab(targetId)
116
123
  const { Runtime } = session
117
- const deadline = Date.now() + timeoutMs
124
+ const startedAt = Date.now()
125
+ const deadline = startedAt + timeoutMs
126
+ let lastProgressAt = startedAt
118
127
  while(Date.now() < deadline) {
119
128
  const r = await Runtime.evaluate({
120
129
  expression: EXTRACT_TOKEN_JS,
@@ -126,6 +135,16 @@ export async function captureFirebaseTokenViaCdp(opts: {
126
135
  return { token: value, closedBrowser: launchedByUs }
127
136
  }
128
137
 
138
+ const now = Date.now()
139
+ if(now - lastProgressAt >= progressIntervalMs) {
140
+ lastProgressAt = now
141
+ const remainingSec = Math.round((deadline - now) / 1000)
142
+ await onProgress(
143
+ `Still waiting for sign-in at ${loginUrl} ` +
144
+ `(${remainingSec}s left before this call times out).`,
145
+ )
146
+ }
147
+
129
148
  await new Promise((resolve) => setTimeout(resolve, pollMs))
130
149
  }
131
150
 
@@ -1,5 +1,9 @@
1
1
  import { agentDisabled } from '../../consts.ts'
2
- import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
2
+ import {
3
+ defineTool,
4
+ type RegisteredTool,
5
+ type ToolContext,
6
+ } from '../../mcp/server.ts'
3
7
  import type { OldIdentity, ReclaimOldClient } from '../client.ts'
4
8
  import { persistIdentity, toEthUid } from '../identity.ts'
5
9
  import { captureFirebaseTokenViaCdp } from '../login-cdp.ts'
@@ -83,7 +87,7 @@ export function authenticateTool(client: ReclaimOldClient): RegisteredTool {
83
87
  },
84
88
  },
85
89
  },
86
- async({ token, ethAddress, loginUrl, timeoutMs, useBrowser }) => {
90
+ async({ token, ethAddress, loginUrl, timeoutMs, useBrowser }, ctx: ToolContext) => {
87
91
  if(token && ethAddress) {
88
92
  throw new Error(
89
93
  'Provide only ONE of `token` or `ethAddress`, not both.',
@@ -136,7 +140,11 @@ export function authenticateTool(client: ReclaimOldClient): RegisteredTool {
136
140
  )
137
141
  }
138
142
 
139
- const result = await captureFirebaseTokenViaCdp({ loginUrl, timeoutMs })
143
+ const result = await captureFirebaseTokenViaCdp({
144
+ loginUrl,
145
+ timeoutMs,
146
+ onProgress: ctx.reportProgress,
147
+ })
140
148
  const identity: OldIdentity = { kind: 'bearer', token: result.token }
141
149
  await validate(identity)
142
150
  persistIdentity(identity)
@@ -3,12 +3,13 @@ import type { ReclaimOldClient } from '../client.ts'
3
3
  import { authenticateTool } from './authenticate.ts'
4
4
  import { getMeProvidersTool } from './me-providers.ts'
5
5
  import { createVersionFromCaptureTool } from './publish.ts'
6
+ import { sessionAnalyticsLogsTool } from './session-analytics-logs.ts'
6
7
 
7
8
  /**
8
9
  * The backend-facing tools for OLD-devtools mode. Mirrors
9
10
  * `buildCredentialTools` / `buildAgentTools`: returns a flat `RegisteredTool`
10
- * list for `registerToolList`. These three replace the builder credential +
11
- * publish tools and the (disabled) generated provider tools.
11
+ * list for `registerToolList`. These replace the builder credential + publish
12
+ * tools and the (disabled) generated provider tools.
12
13
  */
13
14
  export function buildOldBackendTools(
14
15
  client: ReclaimOldClient,
@@ -17,5 +18,6 @@ export function buildOldBackendTools(
17
18
  authenticateTool(client),
18
19
  createVersionFromCaptureTool(client),
19
20
  getMeProvidersTool(client),
21
+ sessionAnalyticsLogsTool(client),
20
22
  ]
21
23
  }
@@ -0,0 +1,33 @@
1
+ import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
2
+ import type { ReclaimOldClient } from '../client.ts'
3
+
4
+ /**
5
+ * Fetch analytics logs for a verification session from the OLD-devtools logs
6
+ * service (`GET https://logs.reclaimprotocol.org/api/analytics-logs/session/
7
+ * :sessionId`). Old mode only — useful for diagnosing why a session failed or
8
+ * stalled. The logs service is a separate host from the devtools API.
9
+ */
10
+ export function sessionAnalyticsLogsTool(
11
+ client: ReclaimOldClient,
12
+ ): RegisteredTool {
13
+ return defineTool<{ sessionId: string }>(
14
+ {
15
+ name: 'session_analytics_logs',
16
+ description:
17
+ 'Fetch analytics logs for a verification session by id from the '
18
+ + 'old-devtools logs service — use to diagnose what happened in a '
19
+ + 'session (matches, claims, failures). Pass the `sessionId`.',
20
+ inputSchema: {
21
+ type: 'object',
22
+ properties: {
23
+ sessionId: {
24
+ type: 'string',
25
+ description: 'The verification session id to fetch logs for.',
26
+ },
27
+ },
28
+ required: ['sessionId'],
29
+ },
30
+ },
31
+ async(args) => client.getSessionAnalyticsLogs(args.sessionId),
32
+ )
33
+ }
@@ -38,7 +38,7 @@ export function resolveAttestorUrl(override?: string): string {
38
38
  )
39
39
  }
40
40
 
41
- /** Map a verification's provider recipe straight to the attestor's HTTP
41
+ /** Map a provider's recipe straight to the attestor's HTTP
42
42
  * params — never synthesized from captured traffic. */
43
43
  export function buildHttpParams(
44
44
  provider: ReclaimProvider,