@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.
- package/README.md +7 -38
- package/lib/cdp/capture-engine.d.ts +7 -0
- package/lib/cdp/capture-engine.js +78 -0
- package/lib/cdp/capture.d.ts +1 -1
- package/lib/cdp/capture.js +1 -1
- package/lib/consts.d.ts +4 -2
- package/lib/consts.js +6 -2
- package/lib/mcp/api/operations.gen.d.ts +8 -0
- package/lib/mcp/api/operations.gen.js +92 -0
- package/lib/mcp/instructions.d.ts +2 -0
- package/lib/mcp/instructions.js +16 -0
- package/lib/mcp/start.js +2 -1
- package/lib/mcp/tools/agent/build.js +4 -0
- package/lib/mcp/tools/agent/how-it-works.d.ts +11 -0
- package/lib/mcp/tools/agent/how-it-works.js +24 -0
- package/lib/old/client.d.ts +10 -0
- package/lib/old/client.js +27 -0
- package/lib/old/to-register.d.ts +5 -3
- package/lib/old/to-register.js +14 -6
- package/lib/old/tools/build.d.ts +2 -2
- package/lib/old/tools/build.js +4 -2
- package/lib/old/tools/publish.js +45 -10
- package/lib/old/tools/session-analytics-logs.d.ts +9 -0
- package/lib/old/tools/session-analytics-logs.js +25 -0
- package/package.json +2 -3
- package/skill/{SKILL.md → SKILL.builder.md} +3 -36
- package/skill/SKILL.old.md +221 -0
- package/skill/builder-mode.md +2 -0
- package/skill/old-mode.md +4 -2
- package/src/cdp/capture-engine.ts +89 -0
- package/src/cdp/capture.ts +5 -1
- package/src/consts.ts +7 -2
- package/src/mcp/api/operations.gen.ts +96 -0
- package/src/mcp/instructions.ts +18 -0
- package/src/mcp/start.ts +2 -0
- package/src/mcp/tools/agent/build.ts +4 -0
- package/src/mcp/tools/agent/how-it-works.ts +32 -0
- package/src/old/client.ts +32 -0
- package/src/old/to-register.ts +15 -6
- package/src/old/tools/build.ts +4 -2
- package/src/old/tools/publish.ts +55 -11
- 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
|
-
###
|
|
114
|
+
### Provider-authoring guide
|
|
115
115
|
|
|
116
|
-
The MCP server is all you need
|
|
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
|
-
|
|
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
|
-
##
|
|
141
|
+
## How it works (guide tool)
|
|
142
142
|
|
|
143
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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 = {
|
package/lib/cdp/capture.d.ts
CHANGED
|
@@ -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>;
|
package/lib/cdp/capture.js
CHANGED
|
@@ -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({
|
package/lib/mcp/instructions.js
CHANGED
|
@@ -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
|
+
}
|
package/lib/old/client.d.ts
CHANGED
|
@@ -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.
|
package/lib/old/to-register.d.ts
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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;
|
package/lib/old/to-register.js
CHANGED
|
@@ -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
|
-
/**
|
|
76
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
109
|
+
requestData: requestDataFrom(providers),
|
|
102
110
|
};
|
|
103
111
|
return { providerConfig, version, versionInfo };
|
|
104
112
|
}
|
package/lib/old/tools/build.d.ts
CHANGED
|
@@ -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
|
|
7
|
-
*
|
|
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[];
|