@reclaimprotocol/agent 0.1.34 → 0.1.36
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 +3 -3
- package/lib/mcp/api/operations.gen.js +340 -150
- package/lib/mcp/instructions.js +3 -3
- package/lib/mcp/start.js +8 -7
- package/lib/mcp/tools/agent/build.js +2 -0
- package/lib/mcp/tools/agent/check-attestor.d.ts +5 -0
- package/lib/mcp/tools/agent/check-attestor.js +37 -0
- package/lib/mcp/tools/agent/live-view.js +42 -18
- package/lib/mcp/tools/agent/proof.d.ts +12 -0
- package/lib/mcp/tools/agent/proof.js +32 -0
- package/lib/mcp/tools/agent/shared.d.ts +2 -1
- package/lib/mcp/tools/agent/shared.js +28 -24
- package/lib/mcp/tools/agent/start-integration.js +5 -4
- package/lib/mcp/tools/verification/build.d.ts +3 -1
- package/lib/mcp/tools/verification/build.js +10 -2
- package/lib/mcp/tools/verification/callback-listener.d.ts +51 -0
- package/lib/mcp/tools/verification/callback-listener.js +163 -0
- package/lib/mcp/tools/verification/index.d.ts +2 -0
- package/lib/mcp/tools/verification/index.js +2 -0
- package/lib/mcp/tools/verification/proxy-callback-events.d.ts +77 -0
- package/lib/mcp/tools/verification/proxy-callback-events.js +383 -0
- package/lib/mcp/tools/verification/read-callback-events.d.ts +3 -0
- package/lib/mcp/tools/verification/read-callback-events.js +135 -0
- package/lib/mcp/tools/verification/result.d.ts +6 -0
- package/lib/mcp/tools/verification/result.js +1 -1
- package/lib/proof/check-attestor.d.ts +67 -0
- package/lib/proof/check-attestor.js +156 -0
- package/package.json +2 -2
- package/skill/SKILL.builder.md +7 -1
- package/skill/SKILL.common.md +21 -9
- package/skill/builder-mode.md +25 -12
- package/skill/start-integration.md +37 -11
- package/skill/verification-client-integration.md +81 -18
- package/src/mcp/api/operations.gen.ts +344 -150
- package/src/mcp/instructions.ts +3 -3
- package/src/mcp/start.ts +9 -7
- package/src/mcp/tools/agent/build.ts +2 -0
- package/src/mcp/tools/agent/check-attestor.ts +49 -0
- package/src/mcp/tools/agent/live-view.ts +42 -18
- package/src/mcp/tools/agent/proof.ts +55 -0
- package/src/mcp/tools/agent/shared.ts +33 -23
- package/src/mcp/tools/agent/start-integration.ts +5 -4
- package/src/mcp/tools/verification/build.ts +10 -2
- package/src/mcp/tools/verification/callback-listener.ts +203 -0
- package/src/mcp/tools/verification/index.ts +2 -0
- package/src/mcp/tools/verification/proxy-callback-events.ts +484 -0
- package/src/mcp/tools/verification/read-callback-events.ts +160 -0
- package/src/mcp/tools/verification/result.ts +1 -1
- package/src/proof/check-attestor.ts +209 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { decryptCallback, ReclaimVerification } from '@reclaimprotocol/client';
|
|
2
|
+
import { apiUrl } from "../../../consts.js";
|
|
3
|
+
import { defineTool } from "../../server.js";
|
|
4
|
+
import { resolveDecryptionKey } from "../authenticate/decrypt-key.js";
|
|
5
|
+
import { ensureListener, orgClient, } from "./callback-listener.js";
|
|
6
|
+
import { resolveOrgSecret } from "./result.js";
|
|
7
|
+
/** Events whose `data` is the signed result (encrypted or plaintext JWS). */
|
|
8
|
+
const RESULT_EVENTS = new Set([
|
|
9
|
+
'verification_success',
|
|
10
|
+
'verification_rejected',
|
|
11
|
+
'verification_error',
|
|
12
|
+
'verification_cancelled',
|
|
13
|
+
]);
|
|
14
|
+
export function readCallbackEventsTool(deps = {}) {
|
|
15
|
+
return defineTool({
|
|
16
|
+
name: 'read_callback_events',
|
|
17
|
+
description: 'Reads the events Reclaim sent to the organization\'s pull '
|
|
18
|
+
+ 'listener, and decodes them: each result event is decrypted and '
|
|
19
|
+
+ 'signature-verified into `result`, and each lifecycle event '
|
|
20
|
+
+ 'returns its `eventData`. Call it to try a provider without an '
|
|
21
|
+
+ 'app or to check what Reclaim sent for a session. Creates the '
|
|
22
|
+
+ 'listener on first use, so it is not read-only. Filter with '
|
|
23
|
+
+ '`sessionId`, or pass the previous `next` as `after` to read on. '
|
|
24
|
+
+ 'Items with a lower sequence can appear late, so for a complete '
|
|
25
|
+
+ 'read use `pending: true`. Returns `{ listener, items, next }`. '
|
|
26
|
+
+ 'Encrypted results need the `RECLAIM_DECRYPT_KEY` that matches '
|
|
27
|
+
+ 'the org keypair. To deliver events to your app\'s callback '
|
|
28
|
+
+ 'route, call `proxy_callback_events`.',
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
sessionId: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
format: 'uuid',
|
|
35
|
+
description: 'Only events of this verification session.',
|
|
36
|
+
},
|
|
37
|
+
after: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
pattern: '^[0-9]{1,19}$',
|
|
40
|
+
description: 'The `next` value of a previous call.',
|
|
41
|
+
},
|
|
42
|
+
limit: {
|
|
43
|
+
type: 'integer',
|
|
44
|
+
minimum: 1,
|
|
45
|
+
maximum: 100,
|
|
46
|
+
description: 'Events to return. Defaults to 50.',
|
|
47
|
+
},
|
|
48
|
+
pending: {
|
|
49
|
+
type: 'boolean',
|
|
50
|
+
description: 'Only events not yet acknowledged. `proxy_callback_events` '
|
|
51
|
+
+ 'acknowledges the events it delivers.',
|
|
52
|
+
},
|
|
53
|
+
orgToken: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
pattern: '^rorg_',
|
|
56
|
+
maxLength: 200,
|
|
57
|
+
description: 'The organization API key (`rorg_…`). Defaults to '
|
|
58
|
+
+ 'the server\'s `RECLAIM_ORG_SECRET`, then the key saved by '
|
|
59
|
+
+ '`issue_org_token`. Never echo it back to the user.',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
annotations: {
|
|
64
|
+
readOnlyHint: false,
|
|
65
|
+
idempotentHint: true,
|
|
66
|
+
openWorldHint: true,
|
|
67
|
+
},
|
|
68
|
+
}, async (args) => {
|
|
69
|
+
const secret = resolveOrgSecret(args.orgToken);
|
|
70
|
+
const client = orgClient(secret, deps);
|
|
71
|
+
const listener = await ensureListener(secret, deps);
|
|
72
|
+
const { data } = await client.call('ListCallbackEvents', {
|
|
73
|
+
query: {
|
|
74
|
+
after: args.after,
|
|
75
|
+
limit: args.limit,
|
|
76
|
+
sessionId: args.sessionId,
|
|
77
|
+
pending: args.pending,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
const hasResults = data.items.some((e) => RESULT_EVENTS.has(e.event));
|
|
81
|
+
const key = hasResults ? await resolveDecryptionKey() : undefined;
|
|
82
|
+
const reclaim = ReclaimVerification.create({
|
|
83
|
+
baseUrl: apiUrl(),
|
|
84
|
+
orgSecret: secret,
|
|
85
|
+
fetch: deps.fetch,
|
|
86
|
+
});
|
|
87
|
+
const items = [];
|
|
88
|
+
for (const item of data.items) {
|
|
89
|
+
items.push(await decode(item));
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
listener,
|
|
93
|
+
items,
|
|
94
|
+
next: data.next,
|
|
95
|
+
...(listener.created
|
|
96
|
+
? {
|
|
97
|
+
_notes: 'Created the pull listener; only sessions created from '
|
|
98
|
+
+ 'now on are recorded. It works only while this MCP server '
|
|
99
|
+
+ 'runs.',
|
|
100
|
+
}
|
|
101
|
+
: {}),
|
|
102
|
+
};
|
|
103
|
+
async function decode(item) {
|
|
104
|
+
const base = {
|
|
105
|
+
id: item.id,
|
|
106
|
+
sessionId: item.sessionId,
|
|
107
|
+
event: item.event,
|
|
108
|
+
deliveryStatus: item.deliveryStatus,
|
|
109
|
+
createdAt: item.createdAt,
|
|
110
|
+
};
|
|
111
|
+
try {
|
|
112
|
+
if (!RESULT_EVENTS.has(item.event)) {
|
|
113
|
+
const received = await reclaim.results.receive(item.envelope);
|
|
114
|
+
return received.kind === 'event'
|
|
115
|
+
? { ...base, eventData: received.eventData }
|
|
116
|
+
: base;
|
|
117
|
+
}
|
|
118
|
+
const jws = await decryptCallback(key, item.envelope);
|
|
119
|
+
const outcome = await reclaim.results.verify(jws, {
|
|
120
|
+
reclaimSessionId: item.sessionId,
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
...base,
|
|
124
|
+
result: { payload: outcome.payload, proofs: outcome.proofs },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
129
|
+
return RESULT_EVENTS.has(item.event)
|
|
130
|
+
? { ...base, resultError: message }
|
|
131
|
+
: { ...base, eventError: message };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { type RegisteredTool } from '../../server.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the org secret used to authenticate verification reads. Auth is a
|
|
4
|
+
* single per-org bearer secret (`rorg_…`), not an eth signature: the call's
|
|
5
|
+
* `orgToken`, else `RECLAIM_ORG_SECRET`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveOrgSecret(orgToken?: string): string;
|
|
2
8
|
/**
|
|
3
9
|
* `get_verification_result` — check a session's status and, once a result has
|
|
4
10
|
* been submitted, pull the stored payload(s) and verify locally. Auth is the
|
|
@@ -25,7 +25,7 @@ const RESULT_EVENTS = new Set([
|
|
|
25
25
|
* single per-org bearer secret (`rorg_…`), not an eth signature: the call's
|
|
26
26
|
* `orgToken`, else `RECLAIM_ORG_SECRET`.
|
|
27
27
|
*/
|
|
28
|
-
function resolveOrgSecret(orgToken) {
|
|
28
|
+
export function resolveOrgSecret(orgToken) {
|
|
29
29
|
const secret = orgToken?.trim() || orgSecret()?.trim() || savedOrgKey();
|
|
30
30
|
assert(secret, new Error('No organization API key. Verification reads authenticate with the org '
|
|
31
31
|
+ 'secret (rorg_…): run issue_org_token to save a key, pass orgToken, '
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attestor reachability check. Proves a public request with a throwaway owner
|
|
3
|
+
* key to show whether the attestor works from this machine, independent of any
|
|
4
|
+
* developer provider or site.
|
|
5
|
+
*/
|
|
6
|
+
import { createClaimOnAttestor } from '@reclaimprotocol/attestor-core';
|
|
7
|
+
import './patch-domhandler.ts';
|
|
8
|
+
/**
|
|
9
|
+
* Default time budget for one check. A live run takes about 8 s; a longer
|
|
10
|
+
* budget risks the MCP client's own tool-call timeout.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CHECK_ATTESTOR_TIMEOUT_MS = 45000;
|
|
13
|
+
/**
|
|
14
|
+
* Request params for the check: the public CoinGecko price request from the
|
|
15
|
+
* attestor-core examples. Source:
|
|
16
|
+
* https://raw.githubusercontent.com/reclaimprotocol/attestor-core/refs/heads/main/example/coin-gecko.json
|
|
17
|
+
*/
|
|
18
|
+
export declare const COIN_GECKO_PARAMS: {
|
|
19
|
+
url: string;
|
|
20
|
+
method: "GET";
|
|
21
|
+
body: string;
|
|
22
|
+
headers: {
|
|
23
|
+
'User-Agent': string;
|
|
24
|
+
'Sec-Fetch-Mode': string;
|
|
25
|
+
'Sec-Fetch-Site': string;
|
|
26
|
+
};
|
|
27
|
+
responseMatches: {
|
|
28
|
+
invert: boolean;
|
|
29
|
+
type: "regex";
|
|
30
|
+
value: string;
|
|
31
|
+
}[];
|
|
32
|
+
responseRedactions: {
|
|
33
|
+
jsonPath: string;
|
|
34
|
+
regex: string;
|
|
35
|
+
xPath: string;
|
|
36
|
+
}[];
|
|
37
|
+
additionalClientOptions: {};
|
|
38
|
+
};
|
|
39
|
+
export type AttestorCheckErrorKind =
|
|
40
|
+
/** Socket, connection, DNS, TLS, or timeout failure. */
|
|
41
|
+
'attestor-unreachable'
|
|
42
|
+
/** The attestor answered and rejected or failed the claim. */
|
|
43
|
+
| 'attestor-error'
|
|
44
|
+
/** Local setup (ZK files) failed before the attestor was contacted. */
|
|
45
|
+
| 'setup-failed';
|
|
46
|
+
export interface AttestorCheckResult {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
attestorUrl: string;
|
|
49
|
+
durationMs: number;
|
|
50
|
+
extractedParameters?: Record<string, string>;
|
|
51
|
+
error?: string;
|
|
52
|
+
errorKind?: AttestorCheckErrorKind;
|
|
53
|
+
}
|
|
54
|
+
/** Creates the claim on the attestor (`createClaimOnAttestor` by default). */
|
|
55
|
+
export type CreateClaim = (args: Parameters<typeof createClaimOnAttestor>[0]) => Promise<unknown>;
|
|
56
|
+
export interface CheckAttestorOptions {
|
|
57
|
+
attestorUrl?: string;
|
|
58
|
+
timeoutMs?: number;
|
|
59
|
+
/** Replaces the network call in tests. */
|
|
60
|
+
createClaim?: CreateClaim;
|
|
61
|
+
/** Replaces the ZK file download in tests. */
|
|
62
|
+
prepare?: () => Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
/** True when `message` looks like a network failure rather than a rejection. */
|
|
65
|
+
export declare function isNetworkError(message: string | undefined): boolean;
|
|
66
|
+
/** Prove the public CoinGecko request once and classify the outcome. */
|
|
67
|
+
export declare function checkAttestor(opts?: CheckAttestorOptions): Promise<AttestorCheckResult>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attestor reachability check. Proves a public request with a throwaway owner
|
|
3
|
+
* key to show whether the attestor works from this machine, independent of any
|
|
4
|
+
* developer provider or site.
|
|
5
|
+
*/
|
|
6
|
+
import { createClaimOnAttestor } from '@reclaimprotocol/attestor-core';
|
|
7
|
+
import { generateKeypair } from '@reclaimprotocol/client';
|
|
8
|
+
import "./patch-domhandler.js";
|
|
9
|
+
import { claimRejection, ensureAttestorCrypto, parseClaim, resolveAttestorUrl, } from "./proof-core.js";
|
|
10
|
+
import { ensureZkFiles } from "./zk-files.js";
|
|
11
|
+
/**
|
|
12
|
+
* Default time budget for one check. A live run takes about 8 s; a longer
|
|
13
|
+
* budget risks the MCP client's own tool-call timeout.
|
|
14
|
+
*/
|
|
15
|
+
export const CHECK_ATTESTOR_TIMEOUT_MS = 45000;
|
|
16
|
+
/* eslint-disable @stylistic/max-len -- the source URL cannot wrap. */
|
|
17
|
+
/**
|
|
18
|
+
* Request params for the check: the public CoinGecko price request from the
|
|
19
|
+
* attestor-core examples. Source:
|
|
20
|
+
* https://raw.githubusercontent.com/reclaimprotocol/attestor-core/refs/heads/main/example/coin-gecko.json
|
|
21
|
+
*/
|
|
22
|
+
/* eslint-enable @stylistic/max-len */
|
|
23
|
+
export const COIN_GECKO_PARAMS = {
|
|
24
|
+
url: 'https://api.coingecko.com/api/v3/simple/price'
|
|
25
|
+
+ '?ids=cardano,solana&vs_currencies=usd',
|
|
26
|
+
method: 'GET',
|
|
27
|
+
body: '',
|
|
28
|
+
headers: {
|
|
29
|
+
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) '
|
|
30
|
+
+ 'AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Safari/604.1',
|
|
31
|
+
'Sec-Fetch-Mode': 'same-origin',
|
|
32
|
+
'Sec-Fetch-Site': 'same-origin',
|
|
33
|
+
},
|
|
34
|
+
responseMatches: [
|
|
35
|
+
{
|
|
36
|
+
invert: false,
|
|
37
|
+
type: 'regex',
|
|
38
|
+
value: 'cardano":{"usd":(?<cardanoPrice>\\d*\\.*\\d*)}',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
invert: false,
|
|
42
|
+
type: 'regex',
|
|
43
|
+
value: 'solana":{"usd":(?<solanaPrice>\\d*\\.*\\d*)}',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
responseRedactions: [
|
|
47
|
+
{ jsonPath: '$.cardano', regex: 'cardano":{"usd":(.*?)}', xPath: '' },
|
|
48
|
+
{ jsonPath: '$.solana', regex: 'solana":{"usd":(.*?)}', xPath: '' },
|
|
49
|
+
],
|
|
50
|
+
additionalClientOptions: {},
|
|
51
|
+
};
|
|
52
|
+
/** The only secret in the example request. */
|
|
53
|
+
const COIN_GECKO_SECRET_HEADERS = {
|
|
54
|
+
Referer: 'https://www.coingecko.com/en/nft',
|
|
55
|
+
};
|
|
56
|
+
/** Prefix of the error raised when the check exceeds its time budget. */
|
|
57
|
+
const CHECK_TIMEOUT_MESSAGE = 'attestor check timed out';
|
|
58
|
+
/**
|
|
59
|
+
* Signals that the attestor connection failed. Bare "timeout", "timed out",
|
|
60
|
+
* and "websocket" are left out: a target site's errors contain them too.
|
|
61
|
+
*/
|
|
62
|
+
const NETWORK_ERROR_PATTERN = new RegExp([
|
|
63
|
+
'connection closed',
|
|
64
|
+
'ERROR_NETWORK_ERROR',
|
|
65
|
+
'ECONNRESET',
|
|
66
|
+
'ECONNREFUSED',
|
|
67
|
+
'ENOTFOUND',
|
|
68
|
+
'EAI_AGAIN',
|
|
69
|
+
'ETIMEDOUT',
|
|
70
|
+
'EHOSTUNREACH',
|
|
71
|
+
'ENETUNREACH',
|
|
72
|
+
'socket hang up',
|
|
73
|
+
'websocket (connection|error|closed|failed)',
|
|
74
|
+
'(failed|unable|could not) to connect',
|
|
75
|
+
CHECK_TIMEOUT_MESSAGE,
|
|
76
|
+
].join('|'), 'i');
|
|
77
|
+
/** True when `message` looks like a network failure rather than a rejection. */
|
|
78
|
+
export function isNetworkError(message) {
|
|
79
|
+
return message !== undefined && NETWORK_ERROR_PATTERN.test(message);
|
|
80
|
+
}
|
|
81
|
+
/** Prove the public CoinGecko request once and classify the outcome. */
|
|
82
|
+
export async function checkAttestor(opts = {}) {
|
|
83
|
+
const attestorUrl = resolveAttestorUrl(opts.attestorUrl);
|
|
84
|
+
const timeoutMs = opts.timeoutMs ?? CHECK_ATTESTOR_TIMEOUT_MS;
|
|
85
|
+
const createClaim = opts.createClaim ?? createClaimOnAttestor;
|
|
86
|
+
const prepare = opts.prepare ?? (async () => {
|
|
87
|
+
ensureAttestorCrypto();
|
|
88
|
+
await ensureZkFiles();
|
|
89
|
+
});
|
|
90
|
+
const startedAt = Date.now();
|
|
91
|
+
const finish = (outcome) => {
|
|
92
|
+
return { ...outcome, attestorUrl, durationMs: Date.now() - startedAt };
|
|
93
|
+
};
|
|
94
|
+
let timer;
|
|
95
|
+
const timeout = new Promise((_, reject) => {
|
|
96
|
+
timer = setTimeout(() => {
|
|
97
|
+
reject(new Error(`${CHECK_TIMEOUT_MESSAGE} after ${timeoutMs} ms`));
|
|
98
|
+
}, timeoutMs);
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
try {
|
|
102
|
+
await Promise.race([prepare(), timeout]);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const error = messageOf(err);
|
|
106
|
+
return finish({
|
|
107
|
+
ok: false,
|
|
108
|
+
error,
|
|
109
|
+
errorKind: error.startsWith(CHECK_TIMEOUT_MESSAGE)
|
|
110
|
+
? 'attestor-unreachable'
|
|
111
|
+
: 'setup-failed',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const claim = await Promise.race([
|
|
115
|
+
createClaim({
|
|
116
|
+
name: 'http',
|
|
117
|
+
params: COIN_GECKO_PARAMS,
|
|
118
|
+
secretParams: { headers: COIN_GECKO_SECRET_HEADERS },
|
|
119
|
+
ownerPrivateKey: generateKeypair().privateKey,
|
|
120
|
+
client: { url: attestorUrl },
|
|
121
|
+
zkEngine: 'stwo',
|
|
122
|
+
}),
|
|
123
|
+
timeout,
|
|
124
|
+
]);
|
|
125
|
+
const rejection = claimRejection(claim);
|
|
126
|
+
if (rejection !== undefined) {
|
|
127
|
+
return finish({
|
|
128
|
+
ok: false,
|
|
129
|
+
error: rejection,
|
|
130
|
+
errorKind: isNetworkError(rejection)
|
|
131
|
+
? 'attestor-unreachable'
|
|
132
|
+
: 'attestor-error',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return finish({
|
|
136
|
+
ok: true,
|
|
137
|
+
extractedParameters: parseClaim(claim).extractedParameters,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
const error = messageOf(err);
|
|
142
|
+
return finish({
|
|
143
|
+
ok: false,
|
|
144
|
+
error,
|
|
145
|
+
errorKind: isNetworkError(error)
|
|
146
|
+
? 'attestor-unreachable'
|
|
147
|
+
: 'attestor-error',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function messageOf(err) {
|
|
155
|
+
return err instanceof Error ? err.message : String(err);
|
|
156
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reclaimprotocol/agent",
|
|
3
3
|
"title": "Reclaim Agent",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.36",
|
|
5
5
|
"description": "Reclaim Protocol tools for AI coding agents. Reclaim lets your app verify data points about its users, such as a follower count, an account balance, or an order history. This package is the Reclaim MCP server: it gives your agent the tools to find, build, test, and fix data verification providers by capturing and replaying browser requests, drafting provider recipes, running zkTLS proofs, and publishing provider versions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./lib/index.js",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
84
84
|
"@noble/hashes": "^2.2.0",
|
|
85
85
|
"@reclaimprotocol/attestor-core": "^5.1.5",
|
|
86
|
-
"@reclaimprotocol/client": "0.1.
|
|
86
|
+
"@reclaimprotocol/client": "0.1.36",
|
|
87
87
|
"@reclaimprotocol/tls": "^0.1.3",
|
|
88
88
|
"chrome-remote-interface": "^0.33.0",
|
|
89
89
|
"domhandler": "^6.0.0",
|
package/skill/SKILL.builder.md
CHANGED
|
@@ -315,6 +315,11 @@ provider JSON. That's the final result.
|
|
|
315
315
|
developer what's happening and drive the bootstrap yourself — see topic
|
|
316
316
|
`credentials`. No restart needed, and the existing `draftId` stays valid.
|
|
317
317
|
|
|
318
|
+
**Network error from `run_proof`.** Read its `diagnosis`. Attestor reachable
|
|
319
|
+
→ the target site likely blocks the attestor's network; call
|
|
320
|
+
`run_proof_via_pod`. Attestor unreachable → `check_attestor` confirms it; the
|
|
321
|
+
fix is on the developer's network. Don't loop retries.
|
|
322
|
+
|
|
318
323
|
<!-- common: prove-tail -->
|
|
319
324
|
|
|
320
325
|
<!-- topic: publish -->
|
|
@@ -456,6 +461,7 @@ or omit `interceptorOptions`). If that fixes the provider, keep interception
|
|
|
456
461
|
off and replace intercepted claims with `window.Reclaim.requestClaim`. Full
|
|
457
462
|
checklist and remedies: topic `interception`.
|
|
458
463
|
|
|
459
|
-
**Proof failures / attestor errors** → topic `prove`.
|
|
464
|
+
**Proof failures / attestor errors** → topic `prove`. For a network error,
|
|
465
|
+
read `run_proof`'s `diagnosis`, then `check_attestor` or `run_proof_via_pod`.
|
|
460
466
|
**Browser won't launch or attach** → topic `browser`.
|
|
461
467
|
**Missing signing key** → topic `credentials`.
|
package/skill/SKILL.common.md
CHANGED
|
@@ -112,13 +112,18 @@ Ask plainly:
|
|
|
112
112
|
|
|
113
113
|
If they say yes:
|
|
114
114
|
|
|
115
|
-
1. Call `share_browser_view` with no arguments. It returns the viewer `port`
|
|
116
|
-
the `command` (`cloudflared tunnel --url http://localhost:<port>`). The
|
|
117
|
-
server does not run cloudflared.
|
|
118
|
-
2.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
1. Call `share_browser_view` with no arguments. It returns the viewer `port`
|
|
116
|
+
and the `command` (`cloudflared tunnel --url http://localhost:<port>`). The
|
|
117
|
+
MCP server does not run cloudflared.
|
|
118
|
+
2. Ask the developer to choose, and start no tunnel until they answer:
|
|
119
|
+
- **They run it (recommended).** They run `! <command>` in this session and
|
|
120
|
+
paste the `https://<words>.trycloudflare.com` URL. The tunnel then lives
|
|
121
|
+
in their shell, and they decide when it closes.
|
|
122
|
+
- **You run it.** They approve you running `command` in the background. If
|
|
123
|
+
`cloudflared` is not installed, ask them to install it; do not install it
|
|
124
|
+
without their approval.
|
|
125
|
+
3. When you run it, read its output for the URL. Never guess or reconstruct the
|
|
126
|
+
hostname.
|
|
122
127
|
4. Call `share_browser_view({ publicUrl })` with that URL. **Relay the returned
|
|
123
128
|
`url` verbatim, on its own line.** It includes `liveview.html?magnify=1`.
|
|
124
129
|
|
|
@@ -138,8 +143,8 @@ If they say yes:
|
|
|
138
143
|
- **Opening it on a phone lays the page out for that phone**, with touch,
|
|
139
144
|
pinch-zoom and the soft keyboard. That is the reason to reach for `container`
|
|
140
145
|
when a site only works on mobile.
|
|
141
|
-
- **Stop
|
|
142
|
-
browser keeps running. `dispose_browser` and `reset_session` stop the
|
|
146
|
+
- **Stop a cloudflared process you started as soon as they're done.** If they
|
|
147
|
+
started it, tell them to stop it. The browser keeps running. `dispose_browser` and `reset_session` stop the
|
|
143
148
|
container itself, but not your cloudflared process.
|
|
144
149
|
- **Expose only the viewer port** that `share_browser_view` returns, never the
|
|
145
150
|
browser's CDP port.
|
|
@@ -381,6 +386,13 @@ supplies are context params.
|
|
|
381
386
|
`result.attestorLogs` and dumps `~/.reclaim/proof-runs/<timestamp>.json`
|
|
382
387
|
(`runLogPath`). Off by default — suggest it for a recurring failure.
|
|
383
388
|
|
|
389
|
+
**Network errors** ("connection closed", `ERROR_NETWORK_ERROR`, timeouts):
|
|
390
|
+
read the result's `diagnosis`. It says whether the attestor is reachable
|
|
391
|
+
from this machine. If it is, the target site likely blocks the attestor's
|
|
392
|
+
network; in builder mode, run `run_proof_via_pod`. If it is not, call
|
|
393
|
+
`check_attestor` after the developer fixes their network. Do not loop
|
|
394
|
+
retries; one `run_proof` per change.
|
|
395
|
+
|
|
384
396
|
**Do not auto-retry with a different `attestorUrl`.** There is one live
|
|
385
397
|
public attestor; backup hostnames (`attestor-2.*`, and so on) **do not
|
|
386
398
|
exist**. `attestorUrl` is only for a developer-supplied private/staging URL.
|
package/skill/builder-mode.md
CHANGED
|
@@ -42,19 +42,17 @@ or `run_proof` against it unless the developer asks.
|
|
|
42
42
|
|
|
43
43
|
### Try a provider without an app
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
to
|
|
45
|
+
Session creation returns 409 while the org has no callback subscription.
|
|
46
|
+
`read_callback_events` creates the org's pull listener, which satisfies that
|
|
47
|
+
check and holds results for you to read. No tunnel or public URL is needed.
|
|
48
48
|
|
|
49
|
-
1.
|
|
50
|
-
events with `create_org_callback_subscription` (for example, a tunnel to a
|
|
51
|
-
local listener that saves the body it receives).
|
|
49
|
+
1. Call `read_callback_events`.
|
|
52
50
|
2. `create_verification_session`, then give the developer `verificationUrl`.
|
|
53
|
-
3. Poll `get_verification_session` until it is terminal.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
3. Poll `get_verification_session` until it is terminal.
|
|
52
|
+
4. Call `read_callback_events({ sessionId })` for the verified result.
|
|
53
|
+
|
|
54
|
+
When you set up a new provider build, call `check_attestor` once. If it fails,
|
|
55
|
+
tell the developer before they spend time on the build.
|
|
58
56
|
|
|
59
57
|
## Secrets
|
|
60
58
|
|
|
@@ -77,11 +75,26 @@ TEE binding. Edit the app with your file tools. Use the HTTP API
|
|
|
77
75
|
`@reclaimprotocol/client`. The only exception is `results.receive` on the
|
|
78
76
|
callback, or when the developer asks for the client package.
|
|
79
77
|
|
|
78
|
+
The app's frontend renders every state and every failure, never only a
|
|
79
|
+
spinner. Follow "UI states" in `how_it_works({ topic: "integration" })`. The
|
|
80
|
+
callback route responds `error.retryable ? 503 : 200` for a thrown
|
|
81
|
+
`ResultVerificationError`; a result that failed verification gets 2xx.
|
|
82
|
+
|
|
83
|
+
For local development, call `proxy_callback_events({ port, path })` to forward
|
|
84
|
+
events to the app's callback route on localhost. The local listener works only
|
|
85
|
+
while this MCP server runs. It keeps events in order per session and retries
|
|
86
|
+
a session the app rejects with backoff, so other sessions keep flowing. When
|
|
87
|
+
its status lists `blocked` sessions, read that session's callback route
|
|
88
|
+
response and logs, and fix the app. Production needs a webhook:
|
|
89
|
+
`create_org_callback_subscription` with the deployed app's public URL. Without
|
|
90
|
+
one, session creation returns 409.
|
|
91
|
+
|
|
80
92
|
Management tools (`list_orgs`, `create_org_callback_subscription`,
|
|
81
93
|
`issue_org_token`) use the developer's sign-in. Session tools
|
|
82
94
|
(`create_verification_session`, `get_verification_session`,
|
|
83
95
|
`list_verification_events`, `list_verification_deliveries`,
|
|
84
|
-
`retry_verification_callback`
|
|
96
|
+
`retry_verification_callback`, `read_callback_events`,
|
|
97
|
+
`proxy_callback_events`) are the production API and use the org key.
|
|
85
98
|
|
|
86
99
|
**Existing `@reclaimprotocol/js-sdk` projects.** If `package.json` depends on
|
|
87
100
|
`@reclaimprotocol/js-sdk`, ask first:
|
|
@@ -12,11 +12,39 @@ Follow this plan end to end. Mark each step done as you go. Do not ask the devel
|
|
|
12
12
|
- [ ] Call `list_orgs`. Use the only organization, or ask the developer which one when there are several.
|
|
13
13
|
<!-- endif -->
|
|
14
14
|
|
|
15
|
-
## 2.
|
|
15
|
+
## 2. Set up the app
|
|
16
|
+
|
|
17
|
+
Setup does not need the provider, so it runs in parallel with step 3 or before it.
|
|
18
|
+
|
|
19
|
+
**Delegation.** If you can run subagents, hand this step to one subagent and start step 3 now: step 3 needs the developer at the browser, step 2 does not. Give the subagent a self-contained brief:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Goal: let this app's users prove "{{goal}}" with Reclaim.
|
|
23
|
+
Repository: <absolute path>. Organization: <org ID>.
|
|
24
|
+
Task: do step 2 of the plan below and report the file paths you changed, the
|
|
25
|
+
callback route URL, and the env variable that holds the provider ID.
|
|
26
|
+
Do not build the provider. Never print or commit the org key.
|
|
27
|
+
<paste the step 2 checklist>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Without subagents, do step 2 first.
|
|
31
|
+
|
|
32
|
+
- [ ] Detect the app's framework and package manager from the repository, and use them. Read `how_it_works({ topic: "integration" })`.
|
|
33
|
+
- [ ] Call `issue_org_token` with the organization, this project's absolute path, and the server env file. It writes the key to that file and never returns it. Never print or commit the key. Confirm the env file is in `.gitignore`.
|
|
34
|
+
- [ ] Add a backend route that creates a verification session (`POST /verifications/sessions`) and returns `verificationUrl`. Read the provider ID from server config (an env variable such as `RECLAIM_PROVIDER_ID`), so the route works before the provider exists.
|
|
35
|
+
- [ ] Add a callback route that verifies each result with `results.receive` from `@reclaimprotocol/client`, then stores only the requested data points. Store every failure on the verification session record and log it server-side: a failure event (`verification_rejected`, `verification_error`, `verification_cancelled`, `session_expired`), or a thrown `ResultVerificationError` (`error.reason` plus each failing proof's `reason`). Respond `error.retryable ? 503 : 200`. A result that failed verification gets 2xx, because retrying can't fix it. Read the exact pattern in `how_it_works({ topic: "integration" })`.
|
|
36
|
+
- [ ] Add a status route the frontend polls. It returns the stored status, data points, and failure reason. While the app has no result, it also reads `GET /verifications/sessions/{sessionId}`, so a session that ended in Builder never stays pending.
|
|
37
|
+
- [ ] Add a frontend Verify button that opens `verificationUrl`, and render every state in "UI states" in `how_it_works({ topic: "integration" })`: idle, waiting in the other tab, verifying, verified (with the data points), failed (plain-language reason and **Try again**), and expired or cancelled (**Try again**). Never leave a spinner as the only feedback.
|
|
38
|
+
- [ ] Start the app. Call `proxy_callback_events({ port, path })` with the callback route's port and path. It forwards the org's verification events to that route on localhost, with no public URL.
|
|
39
|
+
<!-- ifnot:providerIds -->
|
|
40
|
+
- [ ] Call `check_attestor` once. If it fails, tell the developer before they spend time on the provider build: this machine cannot reach the attestor.
|
|
41
|
+
<!-- endif -->
|
|
42
|
+
|
|
43
|
+
## 3. Get the provider
|
|
16
44
|
|
|
17
45
|
<!-- if:providerIds -->
|
|
18
46
|
- [ ] Use these existing providers: {{providerIds}}. Read each with `get_provider` and keep only the data points "{{goal}}" needs. Show the developer and use only those; ignore the rest.
|
|
19
|
-
- [ ] If a provider needs any change (a missing data point, inputs the developer didn't ask for, a different value), make your own copy as the server instructions describe ("Choose the provider"). Otherwise skip to step
|
|
47
|
+
- [ ] If a provider needs any change (a missing data point, inputs the developer didn't ask for, a different value), make your own copy as the server instructions describe ("Choose the provider"). Otherwise skip to step 4.
|
|
20
48
|
<!-- endif -->
|
|
21
49
|
<!-- ifnot:providerIds -->
|
|
22
50
|
Build a new organization-private provider for exactly "{{goal}}". Do not search published providers first.
|
|
@@ -37,13 +65,11 @@ Build a new organization-private provider for exactly "{{goal}}". Do not search
|
|
|
37
65
|
- [ ] Call `dispose_browser` after asking the developer.
|
|
38
66
|
<!-- endif -->
|
|
39
67
|
|
|
40
|
-
##
|
|
68
|
+
## 4. Wire the provider and test end to end
|
|
41
69
|
|
|
42
|
-
- [ ]
|
|
43
|
-
- [ ]
|
|
44
|
-
- [ ]
|
|
45
|
-
- [ ]
|
|
46
|
-
- [ ]
|
|
47
|
-
- [ ]
|
|
48
|
-
- [ ] Add a frontend Verify button that opens `verificationUrl`, and shows the stored result when it arrives.
|
|
49
|
-
- [ ] Run the app, then tell the developer exactly how to test end to end: open the app, click Verify, sign in, and see the stored result.
|
|
70
|
+
- [ ] Set the provider ID in the app's server config.
|
|
71
|
+
- [ ] Confirm the callback route is forwarding: `proxy_callback_events` with no `stop` returns its status. If `blocked` is not empty, the app rejected that session's event: read the callback route's response and logs, fix the app, and check again.
|
|
72
|
+
- [ ] Tell the developer exactly how to test: open the app, click Verify, sign in, and see the stored result. Also test one failure, such as cancelling in the verification tab, and confirm the app shows the failed state with **Try again**.
|
|
73
|
+
- [ ] After they click Verify, read the delivered events from `proxy_callback_events` status (`recent`) or `read_callback_events`. If the result did not arrive, or the app shows an unexpected failure, call `list_verification_deliveries` and fix the cause. Repeat until the app shows the stored result.
|
|
74
|
+
- [ ] Ask the developer to set up the production callback. The local listener works only while this coding agent's MCP server runs; once it stops, the deployed app receives no results. Call `create_org_callback_subscription` with the deployed app's public callback route URL for all five session-ending events: `verification_success`, `verification_rejected`, `verification_error`, `verification_cancelled`, and `session_expired`.
|
|
75
|
+
- [ ] If they have no deployed URL yet, tell them: "When you deploy, ask me to run `create_org_callback_subscription` with your public callback URL, or results will not reach the app."
|