@reclaimprotocol/agent 0.1.2 → 0.1.4

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 (42) hide show
  1. package/README.md +7 -38
  2. package/lib/cdp/capture-engine.d.ts +7 -0
  3. package/lib/cdp/capture-engine.js +78 -0
  4. package/lib/cdp/capture.d.ts +1 -1
  5. package/lib/cdp/capture.js +1 -1
  6. package/lib/consts.d.ts +4 -2
  7. package/lib/consts.js +6 -2
  8. package/lib/mcp/api/operations.gen.d.ts +8 -0
  9. package/lib/mcp/api/operations.gen.js +92 -0
  10. package/lib/mcp/instructions.d.ts +2 -0
  11. package/lib/mcp/instructions.js +16 -0
  12. package/lib/mcp/start.js +2 -1
  13. package/lib/mcp/tools/agent/build.js +4 -0
  14. package/lib/mcp/tools/agent/how-it-works.d.ts +11 -0
  15. package/lib/mcp/tools/agent/how-it-works.js +24 -0
  16. package/lib/old/client.d.ts +10 -0
  17. package/lib/old/client.js +27 -0
  18. package/lib/old/to-register.d.ts +5 -3
  19. package/lib/old/to-register.js +14 -6
  20. package/lib/old/tools/build.d.ts +2 -2
  21. package/lib/old/tools/build.js +4 -2
  22. package/lib/old/tools/publish.js +45 -10
  23. package/lib/old/tools/session-analytics-logs.d.ts +9 -0
  24. package/lib/old/tools/session-analytics-logs.js +25 -0
  25. package/package.json +2 -3
  26. package/skill/{SKILL.md → SKILL.builder.md} +3 -36
  27. package/skill/SKILL.old.md +221 -0
  28. package/skill/builder-mode.md +2 -0
  29. package/skill/old-mode.md +4 -2
  30. package/src/cdp/capture-engine.ts +89 -0
  31. package/src/cdp/capture.ts +5 -1
  32. package/src/consts.ts +7 -2
  33. package/src/mcp/api/operations.gen.ts +96 -0
  34. package/src/mcp/instructions.ts +18 -0
  35. package/src/mcp/start.ts +2 -0
  36. package/src/mcp/tools/agent/build.ts +4 -0
  37. package/src/mcp/tools/agent/how-it-works.ts +32 -0
  38. package/src/old/client.ts +32 -0
  39. package/src/old/to-register.ts +15 -6
  40. package/src/old/tools/build.ts +4 -2
  41. package/src/old/tools/publish.ts +55 -11
  42. package/src/old/tools/session-analytics-logs.ts +33 -0
package/README.md CHANGED
@@ -111,13 +111,13 @@ Defaults target the production old-devtools backend, so **no env is needed for t
111
111
 
112
112
  See [Backends](#backends) for builder mode (`USE_OLD_DEVTOOLS=false`) and the full env-var list.
113
113
 
114
- ### Companion skill (optional, recommended)
114
+ ### Provider-authoring guide
115
115
 
116
- The MCP server is all you need to start — but installing the companion skill makes the agent markedly better at the capture → prove → publish flow (login detection, choosing auth-bound endpoints, OPRF decisions, troubleshooting). See [Skill](#skill) for install steps.
116
+ The MCP server is all you need — it ships a `how_it_works` tool that returns the full capture → prove → publish guide (login detection, choosing auth-bound endpoints, OPRF decisions, troubleshooting). Any MCP client can call it on demand; there's nothing extra to install.
117
117
 
118
118
  ## Using it from your agent
119
119
 
120
- Optional but recommended: install the [skill](#skill) so the agent follows the full authoring flow.
120
+ Tip: have your agent call the `how_it_works` tool first so it follows the full authoring flow.
121
121
 
122
122
  Then just ask in plain language, e.g.:
123
123
 
@@ -138,28 +138,9 @@ The agent orchestrates the tools for you:
138
138
 
139
139
  Your only manual steps are signing in when the browser windows open and confirming the value you want to prove.
140
140
 
141
- ## Skill
141
+ ## How it works (guide tool)
142
142
 
143
- Optional but recommended. The companion skill (`skill/SKILL.md`) is a Claude Code **Agent Skill** that teaches the agent the full mode-aware authoring flow — it ships inside the npm package and is also in this repo.
144
-
145
- ```bash
146
- # macOS / Linux — from a global install of the package
147
- npm i -g @reclaimprotocol/agent
148
- mkdir -p ~/.claude/skills/reclaim-provider
149
- cp "$(npm root -g)/@reclaimprotocol/agent/skill/SKILL.md" ~/.claude/skills/reclaim-provider/SKILL.md
150
-
151
- # …or from a checkout of this repo
152
- cp packages/agent/skill/SKILL.md ~/.claude/skills/reclaim-provider/SKILL.md
153
- ```
154
-
155
- ```powershell
156
- # Windows (PowerShell) — from a global install
157
- npm i -g @reclaimprotocol/agent
158
- New-Item -ItemType Directory -Force $env:USERPROFILE\.claude\skills\reclaim-provider | Out-Null
159
- Copy-Item "$(npm root -g)\@reclaimprotocol\agent\skill\SKILL.md" $env:USERPROFILE\.claude\skills\reclaim-provider\SKILL.md
160
- ```
161
-
162
- Claude Code auto-loads it once it's in `~/.claude/skills/`. **Other MCP clients** (Cursor, opencode, Antigravity, …) don't read `SKILL.md` automatically — the server's built-in instructions already cover the essentials, and you can paste or adapt the skill's content into that client's rules / system-prompt for the same depth.
143
+ The full, mode-aware authoring guide (`skill/SKILL.md`) is served on demand by the **`how_it_works`** MCP tool — call it from any MCP client (Claude Code, Cursor, opencode, Antigravity, …) to get the deep capture → prove → publish workflow, decision frameworks, and troubleshooting. The server's always-on `instructions` cover the essentials; `how_it_works` is the in-depth version, with nothing to install.
163
144
 
164
145
  ## Provider-authoring tools
165
146
 
@@ -285,18 +266,6 @@ claude mcp add reclaim -- node "$(pwd)/packages/agent/lib/mcp-bin.js"
285
266
 
286
267
  To target a specific backend while developing, add an `env` block (Claude Code / `.mcp.json`) or `environment` (opencode) — e.g. `"USE_OLD_DEVTOOLS": "false"` for builder mode. See [Backends](#backends).
287
268
 
288
- ### Skill
289
-
290
- Symlink the source `skill/` folder into `~/.claude/skills/` so edits to `SKILL.md` are picked up live (no re-copy):
291
-
292
- ```bash
293
- # macOS / Linux — from the repo root
294
- ln -s "$(pwd)/packages/agent/skill" ~/.claude/skills/reclaim-provider
295
- ```
296
-
297
- ```powershell
298
- # Windows (PowerShell, Developer Mode or elevated) — from the repo root
299
- New-Item -ItemType SymbolicLink -Path $env:USERPROFILE\.claude\skills\reclaim-provider -Target (Resolve-Path packages\agent\skill)
300
- ```
269
+ ### Authoring guide
301
270
 
302
- Claude Code auto-loads it once linked. opencode doesn't read `SKILL.md` automatically — paste or adapt its content into that client's rules / system-prompt for the same depth.
271
+ `skill/SKILL.builder.md` and `skill/SKILL.old.md` are the sources the `how_it_works` tool serves — one per backend, so an agent is never shown tools its mode doesn't have. The server picks by `USE_OLD_DEVTOOLS`. Edit the relevant file and rebuild (`npm run build`) to update what the tool returns.
@@ -30,6 +30,13 @@ export interface ResponseReceivedLike {
30
30
  * `:`. Stripping at capture time keeps every downstream consumer
31
31
  * (`get_request`, draft, replay, proof) clean by construction. */
32
32
  export declare function stripPseudoHeaders(headers: Record<string, string>): Record<string, string>;
33
+ /** True when the URL's path names a static asset (by file extension; query and
34
+ * hash are ignored, so `chunk.js?v=1` still counts). The capture engine drops
35
+ * these up front — they never carry JSON/HTML a recipe matches. */
36
+ export declare function isStaticAsset(url: string): boolean;
37
+ /** True when a response Content-Type is a static asset (scripts/styles/fonts/
38
+ * images/media). Parameters (`; charset=…`) and case are ignored. */
39
+ export declare function isStaticAssetMime(mimeType: string | undefined): boolean;
33
40
  export interface CaptureSession {
34
41
  captureId: string;
35
42
  requests: Map<string, CapturedRequest>;
@@ -25,6 +25,64 @@ export function stripPseudoHeaders(headers) {
25
25
  }
26
26
  return out;
27
27
  }
28
+ /** Extensions for static assets we never prove over — scripts, styles, fonts,
29
+ * images, media, source maps. A real page pulls hundreds of these; tracking
30
+ * them (and fetching their bodies) just bloats the request map and body store
31
+ * with traffic no recipe ever matches. Note: `.json` is deliberately absent —
32
+ * that's the API shape recipes care about. */
33
+ const STATIC_ASSET_EXTENSIONS = new Set([
34
+ 'js', 'mjs', 'cjs', 'css', 'map',
35
+ 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'avif', 'ico', 'bmp',
36
+ 'woff', 'woff2', 'ttf', 'otf', 'eot',
37
+ 'mp4', 'webm', 'mp3', 'ogg', 'wav',
38
+ ]);
39
+ /** True when the URL's path names a static asset (by file extension; query and
40
+ * hash are ignored, so `chunk.js?v=1` still counts). The capture engine drops
41
+ * these up front — they never carry JSON/HTML a recipe matches. */
42
+ export function isStaticAsset(url) {
43
+ let path;
44
+ try {
45
+ path = new URL(url).pathname;
46
+ }
47
+ catch {
48
+ path = url.split(/[?#]/)[0] ?? url;
49
+ }
50
+ // Extension of the last path segment only, so `/v1.2/data` isn't misread.
51
+ const base = path.slice(path.lastIndexOf('/') + 1);
52
+ const dot = base.lastIndexOf('.');
53
+ if (dot <= 0) {
54
+ return false;
55
+ }
56
+ return STATIC_ASSET_EXTENSIONS.has(base.slice(dot + 1).toLowerCase());
57
+ }
58
+ /** Response Content-Types for static assets. The URL-extension filter misses
59
+ * extension-less bundler/CDN URLs (e.g. `/assets/3b8f1c`), so we also drop by
60
+ * MIME at response time. `text/html`, `application/json`, and `text/plain`
61
+ * are NOT here — those are the document/API shapes recipes match. */
62
+ const STATIC_ASSET_MIME_EXACT = new Set([
63
+ 'text/css',
64
+ 'application/javascript', 'text/javascript', 'application/x-javascript',
65
+ 'application/ecmascript', 'text/ecmascript',
66
+ 'application/wasm',
67
+ 'application/font-woff', 'application/font-woff2',
68
+ 'application/vnd.ms-fontobject', 'application/x-font-ttf',
69
+ ]);
70
+ const STATIC_ASSET_MIME_PREFIXES = ['image/', 'font/', 'audio/', 'video/'];
71
+ /** True when a response Content-Type is a static asset (scripts/styles/fonts/
72
+ * images/media). Parameters (`; charset=…`) and case are ignored. */
73
+ export function isStaticAssetMime(mimeType) {
74
+ if (!mimeType) {
75
+ return false;
76
+ }
77
+ const m = mimeType.toLowerCase().split(';')[0].trim();
78
+ if (!m) {
79
+ return false;
80
+ }
81
+ if (STATIC_ASSET_MIME_PREFIXES.some((p) => m.startsWith(p))) {
82
+ return true;
83
+ }
84
+ return STATIC_ASSET_MIME_EXACT.has(m);
85
+ }
28
86
  export function createCaptureEngine(opts) {
29
87
  const captureId = randomUUID();
30
88
  const requests = new Map();
@@ -79,6 +137,13 @@ export function createCaptureEngine(opts) {
79
137
  pendingResponseExtra.set(requestId, { ...buffered, ...clean });
80
138
  }
81
139
  function onRequestWillBeSent(p) {
140
+ // Static assets never match a recipe — never track them (so we don't
141
+ // fetch their bodies or grow the store). Drop any extra-info that raced
142
+ // ahead of this event so it can't linger.
143
+ if (isStaticAsset(p.request.url)) {
144
+ pendingRequestExtra.delete(p.requestId);
145
+ return;
146
+ }
82
147
  const f = {
83
148
  requestId: p.requestId,
84
149
  method: p.request.method,
@@ -100,6 +165,19 @@ export function createCaptureEngine(opts) {
100
165
  function onResponseReceived(p) {
101
166
  const f = inFlight.get(p.requestId);
102
167
  if (!f) {
168
+ // No in-flight entry → the request was skipped (e.g. a static asset)
169
+ // or never seen. Discard any buffered response extra-info so a skipped
170
+ // asset can't leave a dangling entry.
171
+ pendingResponseExtra.delete(p.requestId);
172
+ return;
173
+ }
174
+ // Second-line asset filter by Content-Type: catches extension-less
175
+ // bundler/CDN URLs the request-time URL filter can't. Forget the
176
+ // in-flight entry so it never reaches `requests` and a later
177
+ // loadingFinished/extra-info for it becomes a no-op.
178
+ if (isStaticAssetMime(p.response.mimeType)) {
179
+ inFlight.delete(p.requestId);
180
+ pendingResponseExtra.delete(p.requestId);
103
181
  return;
104
182
  }
105
183
  const captured = {
@@ -6,6 +6,6 @@
6
6
  */
7
7
  import type CDP from 'chrome-remote-interface';
8
8
  import { type CaptureSession, type StartCaptureOptions } from './capture-engine.ts';
9
- export { stripPseudoHeaders } from './capture-engine.ts';
9
+ export { isStaticAsset, isStaticAssetMime, stripPseudoHeaders, } from './capture-engine.ts';
10
10
  export type { CaptureSession, StartCaptureOptions } from './capture-engine.ts';
11
11
  export declare function startCapture(tab: CDP.Client, opts?: StartCaptureOptions): Promise<CaptureSession>;
@@ -1,5 +1,5 @@
1
1
  import { createCaptureEngine, } from "./capture-engine.js";
2
- export { stripPseudoHeaders } from "./capture-engine.js";
2
+ export { isStaticAsset, isStaticAssetMime, stripPseudoHeaders, } from "./capture-engine.js";
3
3
  export async function startCapture(tab, opts = {}) {
4
4
  const engine = createCaptureEngine({
5
5
  ...(opts.maxBodyBytes !== undefined && { maxBodyBytes: opts.maxBodyBytes }),
package/lib/consts.d.ts CHANGED
@@ -16,12 +16,12 @@
16
16
  * Each var's default/fallback literal is co-located with its getter.
17
17
  */
18
18
  import { DEFAULT_BASE_URL } from '@reclaimprotocol/client';
19
- import { DEFAULT_OLD_API_URL } from './old/client.ts';
19
+ import { DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL } from './old/client.ts';
20
20
  /** Public attestor websocket URL — fallback for `RECLAIM_ATTESTOR_URL`. */
21
21
  export declare const FALLBACK_ATTESTOR_URL = "wss://attestor.reclaimprotocol.org:444/ws";
22
22
  /** The devtools dashboard login page — fallback for `RECLAIM_OLD_LOGIN_URL`. */
23
23
  export declare const DEFAULT_LOGIN_URL = "https://dev.reclaimprotocol.org";
24
- export { DEFAULT_BASE_URL, DEFAULT_OLD_API_URL };
24
+ export { DEFAULT_BASE_URL, DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL };
25
25
  /** Pino log level. Read once at logger construction, so an eager const is
26
26
  * safe (no test mutates LOG_LEVEL at runtime). */
27
27
  export declare const LOG_LEVEL: string;
@@ -45,6 +45,8 @@ export declare function chromePath(): string | undefined;
45
45
  export declare function agentDisabled(): boolean;
46
46
  /** Old-devtools backend base URL. */
47
47
  export declare function oldApiUrl(): string;
48
+ /** Old-devtools analytics-logs service base URL (separate host). */
49
+ export declare function oldLogsUrl(): string;
48
50
  /** Builder backend base URL. Shared by mcp/start and the verification tool. */
49
51
  export declare function apiUrl(): string;
50
52
  /** Explicit builder API token (overrides the cached session token). */
package/lib/consts.js CHANGED
@@ -18,7 +18,7 @@
18
18
  import { DEFAULT_BASE_URL } from '@reclaimprotocol/client';
19
19
  import { homedir } from 'node:os';
20
20
  import { join } from 'node:path';
21
- import { DEFAULT_OLD_API_URL } from "./old/client.js";
21
+ import { DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL } from "./old/client.js";
22
22
  // ---------------------------------------------------------------------------
23
23
  // Default / fallback literals (used only as env fallbacks).
24
24
  // ---------------------------------------------------------------------------
@@ -28,7 +28,7 @@ export const FALLBACK_ATTESTOR_URL = 'wss://attestor.reclaimprotocol.org:444/ws'
28
28
  export const DEFAULT_LOGIN_URL = 'https://dev.reclaimprotocol.org';
29
29
  // Re-export the two non-env-fallback defaults so callers can keep importing
30
30
  // their URL defaults from a single place if they wish.
31
- export { DEFAULT_BASE_URL, DEFAULT_OLD_API_URL };
31
+ export { DEFAULT_BASE_URL, DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL };
32
32
  // ---------------------------------------------------------------------------
33
33
  // Logging.
34
34
  // ---------------------------------------------------------------------------
@@ -79,6 +79,10 @@ export function agentDisabled() {
79
79
  export function oldApiUrl() {
80
80
  return process.env.RECLAIM_OLD_API_URL || DEFAULT_OLD_API_URL;
81
81
  }
82
+ /** Old-devtools analytics-logs service base URL (separate host). */
83
+ export function oldLogsUrl() {
84
+ return process.env.RECLAIM_OLD_LOGS_URL || DEFAULT_OLD_LOGS_URL;
85
+ }
82
86
  /** Builder backend base URL. Shared by mcp/start and the verification tool. */
83
87
  export function apiUrl() {
84
88
  return process.env.RECLAIM_API_URL || DEFAULT_BASE_URL;
@@ -75,6 +75,14 @@ export declare const MCP_TOOLS: {
75
75
  description: string;
76
76
  inputSchema: import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
77
77
  };
78
+ DiscoverDataPoints: {
79
+ description: string;
80
+ inputSchema: import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
81
+ };
82
+ DiscoverResolve: {
83
+ description: string;
84
+ inputSchema: import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
85
+ };
78
86
  GetAttachSession: {
79
87
  description: string;
80
88
  inputSchema: import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
@@ -1421,6 +1421,98 @@ export const MCP_TOOLS = {
1421
1421
  "additionalProperties": false
1422
1422
  }),
1423
1423
  },
1424
+ "DiscoverDataPoints": {
1425
+ description: "List the verifiable data points on a website\n\nThe data points existing providers can prove on a website — the menu to pick from before `GET /discover/resolve`. Optionally filter by `q`.",
1426
+ inputSchema: toZod({
1427
+ "type": "object",
1428
+ "properties": {
1429
+ "params": {
1430
+ "type": "object",
1431
+ "properties": {}
1432
+ },
1433
+ "query": {
1434
+ "type": "object",
1435
+ "properties": {
1436
+ "domain": {
1437
+ "type": "string",
1438
+ "format": "hostname",
1439
+ "minLength": 1,
1440
+ "maxLength": 200
1441
+ },
1442
+ "q": {
1443
+ "type": "string",
1444
+ "maxLength": 200
1445
+ }
1446
+ },
1447
+ "required": [
1448
+ "domain"
1449
+ ],
1450
+ "additionalProperties": false
1451
+ },
1452
+ "body": {
1453
+ "type": "object"
1454
+ }
1455
+ },
1456
+ "required": [
1457
+ "params",
1458
+ "query"
1459
+ ],
1460
+ "additionalProperties": false
1461
+ }),
1462
+ },
1463
+ "DiscoverResolve": {
1464
+ description: "Resolve a website + data points to verification providers\n\nProvider resolution: given a website and the data points the user wants their users to prove, return the smallest set of existing providers whose data points COVER the selection (greedy combine), the `requiredContext` inputs the consumer must supply, and any `uncovered` points no provider exposes yet (`needsCreation` is true then — author a provider with the agent tools). When the chosen providers can be fused into one (same start page, no clashing page scripts) `merge.mergeable` is true.\n\nAgent flow: pick a website (`GET /discover/data-points` lists what's verifiable there), confirm the data points with the user, call this to get the `providers` + `requiredContext`, collect those context values from the user, then `POST /verifications/sessions` with `providers` (pass every id) and `context` to mint a verification.",
1465
+ inputSchema: toZod({
1466
+ "type": "object",
1467
+ "properties": {
1468
+ "params": {
1469
+ "type": "object",
1470
+ "properties": {}
1471
+ },
1472
+ "query": {
1473
+ "type": "object",
1474
+ "properties": {
1475
+ "domain": {
1476
+ "type": "string",
1477
+ "format": "hostname",
1478
+ "minLength": 1,
1479
+ "maxLength": 200
1480
+ },
1481
+ "dataPoints": {
1482
+ "type": "array",
1483
+ "minItems": 1,
1484
+ "maxItems": 200,
1485
+ "items": {
1486
+ "type": "string",
1487
+ "minLength": 1,
1488
+ "maxLength": 200
1489
+ }
1490
+ },
1491
+ "mode": {
1492
+ "type": "string",
1493
+ "enum": [
1494
+ "combine",
1495
+ "ai"
1496
+ ]
1497
+ }
1498
+ },
1499
+ "required": [
1500
+ "domain",
1501
+ "dataPoints"
1502
+ ],
1503
+ "additionalProperties": false
1504
+ },
1505
+ "body": {
1506
+ "type": "object"
1507
+ }
1508
+ },
1509
+ "required": [
1510
+ "params",
1511
+ "query"
1512
+ ],
1513
+ "additionalProperties": false
1514
+ }),
1515
+ },
1424
1516
  "GetAttachSession": {
1425
1517
  description: "Poll a pairing session\n\nAnonymous poll endpoint used by the CLI / MCP while it waits for the human to complete the dashboard step. Once `status` is `LINKED`, the response includes a `token` (2-day bearer token) the CLI / MCP can use for authenticated API calls. Expired or unknown codes return `404`.",
1426
1518
  inputSchema: toZod({
@@ -1 +1,3 @@
1
1
  export declare const BUILDER_MODE_INSTRUCTIONS: string;
2
+ export declare const PROVIDER_GUIDE_BUILDER: string;
3
+ export declare const PROVIDER_GUIDE_OLD: string;
@@ -9,3 +9,19 @@
9
9
  import { readFileSync } from 'node:fs';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  export const BUILDER_MODE_INSTRUCTIONS = readFileSync(fileURLToPath(new URL('../../skill/builder-mode.md', import.meta.url)), 'utf8').trim();
12
+ /**
13
+ * The full provider-authoring guide — the deep capture→prove→publish workflow,
14
+ * decision frameworks, and troubleshooting. Served on demand by the
15
+ * `how_it_works` MCP tool so every client (not just Claude Code, which
16
+ * auto-loads skills) can pull it. There's a guide PER MODE so an agent is never
17
+ * shown auth/publish tools that don't exist in its backend: the server picks
18
+ * one by `oldMode` (see `mcp/start.ts`). Same `../../skill/` path resolution as
19
+ * the instructions above; any leading frontmatter is stripped defensively.
20
+ */
21
+ function readGuide(file) {
22
+ return readFileSync(fileURLToPath(new URL(file, import.meta.url)), 'utf8')
23
+ .replace(/^---\n[\s\S]*?\n---\n/, '')
24
+ .trim();
25
+ }
26
+ export const PROVIDER_GUIDE_BUILDER = readGuide('../../skill/SKILL.builder.md');
27
+ export const PROVIDER_GUIDE_OLD = readGuide('../../skill/SKILL.old.md');
package/lib/mcp/start.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { ReclaimClient } from '@reclaimprotocol/client';
4
- import { agentDisabled, apiToken, apiUrl, oldApiUrl, useOldDevtools, } from "../consts.js";
4
+ import { agentDisabled, apiToken, apiUrl, oldApiUrl, oldLogsUrl, useOldDevtools, } from "../consts.js";
5
5
  import { ReclaimOldClient } from "../old/client.js";
6
6
  import { resolveInitialIdentity } from "../old/identity.js";
7
7
  import { OLD_MODE_INSTRUCTIONS } from "../old/instructions.js";
@@ -30,6 +30,7 @@ export async function startMcpServer() {
30
30
  // get_me_providers instead.
31
31
  const oldClient = new ReclaimOldClient({
32
32
  baseUrl: oldApiUrl(),
33
+ logsBaseUrl: oldLogsUrl(),
33
34
  identity: resolveInitialIdentity(),
34
35
  });
35
36
  registerToolList(server, buildLocalCredentialTools());
@@ -14,6 +14,7 @@ import { attachTool } from "./attach.js";
14
14
  import { captureTools } from "./capture.js";
15
15
  import { browserTools } from "./eval.js";
16
16
  import { findTool } from "./find.js";
17
+ import { howItWorksTool } from "./how-it-works.js";
17
18
  import { proofTool } from "./proof.js";
18
19
  import { makeSecretStore, proposeTool, } from "./propose.js";
19
20
  import { createVersionFromCaptureTool } from "./publish.js";
@@ -35,6 +36,9 @@ export function buildAgentTools(client) {
35
36
  const state = buildAgentState();
36
37
  ensureAttestorCrypto();
37
38
  const tools = [];
39
+ // Stateless docs tool — serve the mode-specific authoring guide to any
40
+ // client. No `client` ⇒ old-devtools mode (see `mcp/start.ts`).
41
+ tools.push(howItWorksTool(!client));
38
42
  tools.push(attachTool(state.attach));
39
43
  for (const t of captureTools(state.attach, state.captures)) {
40
44
  tools.push(t);
@@ -0,0 +1,11 @@
1
+ import { type RegisteredTool } from '../../server.ts';
2
+ /**
3
+ * Serves the full provider-authoring guide on demand. The MCP server's
4
+ * always-on `instructions` are a terse tool-flow reference; this tool returns
5
+ * the deep version (workflow, decision frameworks, gotchas, "never do" rules)
6
+ * so any MCP client — not just Claude Code, which alone auto-loads skills — can
7
+ * pull it. `oldMode` selects the backend-specific guide so the agent is never
8
+ * told to call auth/publish tools that aren't registered in its mode.
9
+ * Stateless: callable before `attach_chrome`.
10
+ */
11
+ export declare function howItWorksTool(oldMode: boolean): RegisteredTool;
@@ -0,0 +1,24 @@
1
+ import { PROVIDER_GUIDE_BUILDER, PROVIDER_GUIDE_OLD, } from "../../instructions.js";
2
+ import { defineTool } from "../../server.js";
3
+ /**
4
+ * Serves the full provider-authoring guide on demand. The MCP server's
5
+ * always-on `instructions` are a terse tool-flow reference; this tool returns
6
+ * the deep version (workflow, decision frameworks, gotchas, "never do" rules)
7
+ * so any MCP client — not just Claude Code, which alone auto-loads skills — can
8
+ * pull it. `oldMode` selects the backend-specific guide so the agent is never
9
+ * told to call auth/publish tools that aren't registered in its mode.
10
+ * Stateless: callable before `attach_chrome`.
11
+ */
12
+ export function howItWorksTool(oldMode) {
13
+ const guide = oldMode ? PROVIDER_GUIDE_OLD : PROVIDER_GUIDE_BUILDER;
14
+ return defineTool({
15
+ name: 'how_it_works',
16
+ description: 'Return the full Reclaim provider-authoring guide for THIS server: '
17
+ + 'the capture → draft → replay → prove → publish workflow, decision '
18
+ + 'frameworks (login detection, OPRF privacy, context vs bare params), '
19
+ + 'multi-request handling, troubleshooting, and what never to do. '
20
+ + 'Call this BEFORE building a provider unless you are already '
21
+ + 'fluent in the flow.',
22
+ inputSchema: { type: 'object', properties: {} },
23
+ }, async () => ({ guide }));
24
+ }
@@ -2,6 +2,9 @@
2
2
  * `/api` route prefix itself, so this is the bare origin. Override with
3
3
  * `RECLAIM_OLD_API_URL`. */
4
4
  export declare const DEFAULT_OLD_API_URL = "https://devapi.reclaimprotocol.org";
5
+ /** Default origin of the OLD-devtools analytics-logs service — a SEPARATE host
6
+ * from the devtools API. Override with `RECLAIM_OLD_LOGS_URL`. */
7
+ export declare const DEFAULT_OLD_LOGS_URL = "https://logs.reclaimprotocol.org";
5
8
  /**
6
9
  * Externally-supplied caller identity. We do NOT mint these — the dev
7
10
  * provides one:
@@ -53,8 +56,10 @@ export interface MyProvidersQuery {
53
56
  export declare class ReclaimOldClient {
54
57
  #private;
55
58
  readonly baseUrl: string;
59
+ readonly logsBaseUrl: string;
56
60
  constructor(opts?: {
57
61
  baseUrl?: string;
62
+ logsBaseUrl?: string;
58
63
  identity?: OldIdentity;
59
64
  fetch?: typeof fetch;
60
65
  });
@@ -66,5 +71,10 @@ export declare class ReclaimOldClient {
66
71
  registerProvider(body: RegisterProviderBody): Promise<unknown>;
67
72
  updateProviderConfig(providerId: string, body: UpdateProviderConfigBody): Promise<unknown>;
68
73
  listVersions(providerId: string): Promise<unknown>;
74
+ /** Analytics logs for a verification session, from the logs service (a
75
+ * DIFFERENT host than the devtools API — hence its own fetch, not
76
+ * `#request`). Attaches the caller identity when one is set; queried
77
+ * anonymously otherwise. */
78
+ getSessionAnalyticsLogs(sessionId: string): Promise<unknown>;
69
79
  getMyProviders(query?: MyProvidersQuery): Promise<unknown>;
70
80
  }
package/lib/old/client.js CHANGED
@@ -4,6 +4,9 @@ import { NAME, VERSION } from "../pkg.js";
4
4
  * `/api` route prefix itself, so this is the bare origin. Override with
5
5
  * `RECLAIM_OLD_API_URL`. */
6
6
  export const DEFAULT_OLD_API_URL = 'https://devapi.reclaimprotocol.org';
7
+ /** Default origin of the OLD-devtools analytics-logs service — a SEPARATE host
8
+ * from the devtools API. Override with `RECLAIM_OLD_LOGS_URL`. */
9
+ export const DEFAULT_OLD_LOGS_URL = 'https://logs.reclaimprotocol.org';
7
10
  /**
8
11
  * Minimal REST client for the OLD devtools Express backend (base `/api`).
9
12
  * Deliberately NOT a clone of `@reclaimprotocol/client`'s operationId
@@ -16,10 +19,13 @@ export const DEFAULT_OLD_API_URL = 'https://devapi.reclaimprotocol.org';
16
19
  */
17
20
  export class ReclaimOldClient {
18
21
  baseUrl;
22
+ logsBaseUrl;
19
23
  #identity;
20
24
  #fetch;
21
25
  constructor(opts = {}) {
22
26
  this.baseUrl = (opts.baseUrl || DEFAULT_OLD_API_URL).replace(/\/+$/, '');
27
+ this.logsBaseUrl = (opts.logsBaseUrl || DEFAULT_OLD_LOGS_URL)
28
+ .replace(/\/+$/, '');
23
29
  this.#identity = opts.identity;
24
30
  this.#fetch = opts.fetch ?? fetch;
25
31
  }
@@ -43,6 +49,27 @@ export class ReclaimOldClient {
43
49
  listVersions(providerId) {
44
50
  return this.#request('GET', `/providers/${encodeURIComponent(providerId)}/versions`, { auth: true });
45
51
  }
52
+ /** Analytics logs for a verification session, from the logs service (a
53
+ * DIFFERENT host than the devtools API — hence its own fetch, not
54
+ * `#request`). Attaches the caller identity when one is set; queried
55
+ * anonymously otherwise. */
56
+ async getSessionAnalyticsLogs(sessionId) {
57
+ const url = `${this.logsBaseUrl}/api/analytics-logs/session/`
58
+ + encodeURIComponent(sessionId);
59
+ const headers = {
60
+ Accept: 'application/json',
61
+ 'User-Agent': `${NAME}/${VERSION}`,
62
+ };
63
+ if (this.#identity) {
64
+ headers[this.#authHeaderName()] = this.#authHeaderValue();
65
+ }
66
+ const res = await this.#fetch(url, { method: 'GET', headers });
67
+ const json = await this.#parseBody(res);
68
+ if (!res.ok) {
69
+ throw this.#asBoom(res, json);
70
+ }
71
+ return json;
72
+ }
46
73
  getMyProviders(query = {}) {
47
74
  // Every one of these query params is REQUIRED by the backend
48
75
  // validator (missing/invalid → 400), so default them all here.
@@ -23,12 +23,14 @@ export interface OldProviderOpts {
23
23
  useProxy?: boolean;
24
24
  providerType?: 'PRIVATE' | 'PUBLIC';
25
25
  }
26
- /** Body for `POST /api/providers/register` — new provider + v1.0.0. */
27
- export declare function providerToRegisterBody(provider: ReclaimProvider, opts: OldProviderOpts): RegisterProviderBody;
26
+ /** Body for `POST /api/providers/register` — new provider + v1.0.0. Every
27
+ * drafted request becomes a `requestData` entry; provider-level fields (name)
28
+ * come from the first draft. */
29
+ export declare function providerToRegisterBody(providers: ReclaimProvider[], opts: OldProviderOpts): RegisterProviderBody;
28
30
  /** Body for `POST /api/providers/:providerId/config` — adds a new version to
29
31
  * an EXISTING provider. `version` must not already exist; `versionInfo` is
30
32
  * required and must be ≥10 chars (enforced by the backend). */
31
- export declare function providerToConfigBody(provider: ReclaimProvider, opts: OldProviderOpts, version: string, versionInfo: string): UpdateProviderConfigBody;
33
+ export declare function providerToConfigBody(providers: ReclaimProvider[], opts: OldProviderOpts, version: string, versionInfo: string): UpdateProviderConfigBody;
32
34
  /** Minimal version triplet as the old backend stores it. */
33
35
  export interface SemverTriplet {
34
36
  major: number;
@@ -72,16 +72,24 @@ function requestDataFromProvider(provider) {
72
72
  // it ourselves would risk drifting from the server's own extraction.
73
73
  return requestData;
74
74
  }
75
- /** Body for `POST /api/providers/register` — new provider + v1.0.0. */
76
- export function providerToRegisterBody(provider, opts) {
75
+ /** One `requestData` entry per drafted provider. A multi-request provider
76
+ * becomes parallel entries that the old backend runs together at verification
77
+ * (the same way the builder version carries multiple `requests`). */
78
+ function requestDataFrom(providers) {
79
+ return providers.map(requestDataFromProvider);
80
+ }
81
+ /** Body for `POST /api/providers/register` — new provider + v1.0.0. Every
82
+ * drafted request becomes a `requestData` entry; provider-level fields (name)
83
+ * come from the first draft. */
84
+ export function providerToRegisterBody(providers, opts) {
77
85
  const body = {
78
- name: provider.name,
86
+ name: providers[0].name,
79
87
  loginUrl: opts.initialUrl,
80
88
  providerType: opts.providerType ?? 'PRIVATE',
81
89
  // Web-settings defaults mirror the builder openapi defaults.
82
90
  geoLocation: opts.geoLocation ?? DEFAULT_GEO_LOCATION,
83
91
  useProxy: opts.useProxy ?? DEFAULT_USE_PROXY,
84
- requestData: [requestDataFromProvider(provider)],
92
+ requestData: requestDataFrom(providers),
85
93
  };
86
94
  if (opts.description !== undefined) {
87
95
  body.description = opts.description;
@@ -91,14 +99,14 @@ export function providerToRegisterBody(provider, opts) {
91
99
  /** Body for `POST /api/providers/:providerId/config` — adds a new version to
92
100
  * an EXISTING provider. `version` must not already exist; `versionInfo` is
93
101
  * required and must be ≥10 chars (enforced by the backend). */
94
- export function providerToConfigBody(provider, opts, version, versionInfo) {
102
+ export function providerToConfigBody(providers, opts, version, versionInfo) {
95
103
  // `useProxy` is a Provider-level flag on the old backend, not part of a
96
104
  // version's providerConfig — so the config (new-version) path only carries
97
105
  // `geoLocation`. It defaults the same way as register.
98
106
  const providerConfig = {
99
107
  loginUrl: opts.initialUrl,
100
108
  geoLocation: opts.geoLocation ?? DEFAULT_GEO_LOCATION,
101
- requestData: [requestDataFromProvider(provider)],
109
+ requestData: requestDataFrom(providers),
102
110
  };
103
111
  return { providerConfig, version, versionInfo };
104
112
  }
@@ -3,7 +3,7 @@ import type { ReclaimOldClient } from '../client.ts';
3
3
  /**
4
4
  * The backend-facing tools for OLD-devtools mode. Mirrors
5
5
  * `buildCredentialTools` / `buildAgentTools`: returns a flat `RegisteredTool`
6
- * list for `registerToolList`. These three replace the builder credential +
7
- * publish tools and the (disabled) generated provider tools.
6
+ * list for `registerToolList`. These replace the builder credential + publish
7
+ * tools and the (disabled) generated provider tools.
8
8
  */
9
9
  export declare function buildOldBackendTools(client: ReclaimOldClient): RegisteredTool[];