@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
package/lib/mcp/start.js
CHANGED
|
@@ -11,7 +11,7 @@ import { ReclaimSdkClient } from "../old/sdk-client.js";
|
|
|
11
11
|
import { buildOldBackendTools } from "../old/tools/build.js";
|
|
12
12
|
import { NAME, VERSION } from "../pkg.js";
|
|
13
13
|
import { registerTools } from "./api/tools.js";
|
|
14
|
-
import { buildAgentState, buildAgentTools, buildBuilderAuthenticationTools, buildBuilderOrganizationTools, buildCredentialTools, buildLocalCredentialTools, buildModeTools, buildVerificationTools, disposeAgentState, readSession, } from "./tools/index.js";
|
|
14
|
+
import { buildAgentState, buildAgentTools, buildBuilderAuthenticationTools, buildBuilderOrganizationTools, buildCredentialTools, buildLocalCredentialTools, buildModeTools, buildVerificationTools, disposeAgentState, readSession, releaseAllListeners, stopAllForwarders, } from "./tools/index.js";
|
|
15
15
|
import { BUILDER_MODE_INSTRUCTIONS } from "./instructions.js";
|
|
16
16
|
import { registerToolList } from "./server.js";
|
|
17
17
|
export async function startMcpServer() {
|
|
@@ -94,7 +94,7 @@ export async function startMcpServer() {
|
|
|
94
94
|
// the session can end without a single tool call: the developer closes their
|
|
95
95
|
// agent, the client kills the server, stdin drops. Nothing ran here before,
|
|
96
96
|
// which left a container up after the session that made it was gone.
|
|
97
|
-
if (agentToolsEnabled) {
|
|
97
|
+
if (agentToolsEnabled || !oldMode) {
|
|
98
98
|
installShutdownHooks(() => agentState);
|
|
99
99
|
}
|
|
100
100
|
const transport = new StdioServerTransport();
|
|
@@ -117,10 +117,7 @@ function installShutdownHooks(state) {
|
|
|
117
117
|
}
|
|
118
118
|
shuttingDown = true;
|
|
119
119
|
const current = state();
|
|
120
|
-
|
|
121
|
-
process.exit(0);
|
|
122
|
-
}
|
|
123
|
-
LOGGER.info({ reason }, 'releasing the browser before exit');
|
|
120
|
+
LOGGER.info({ reason }, 'releasing resources before exit');
|
|
124
121
|
// Bounded: a wedged `docker stop` must not keep the process alive, but
|
|
125
122
|
// nor should we skip cleanup that would have finished in a second.
|
|
126
123
|
const timer = setTimeout(() => {
|
|
@@ -128,7 +125,11 @@ function installShutdownHooks(state) {
|
|
|
128
125
|
process.exit(0);
|
|
129
126
|
}, SHUTDOWN_GRACE_MS);
|
|
130
127
|
timer.unref();
|
|
131
|
-
void
|
|
128
|
+
void Promise.all([
|
|
129
|
+
stopAllForwarders(),
|
|
130
|
+
releaseAllListeners(),
|
|
131
|
+
current && disposeAgentState(current),
|
|
132
|
+
])
|
|
132
133
|
.catch((err) => {
|
|
133
134
|
LOGGER.warn({ err }, 'teardown failed');
|
|
134
135
|
})
|
|
@@ -14,6 +14,7 @@ import { ensureAttestorCrypto } from "../../../proof/proof-core.js";
|
|
|
14
14
|
import { analyzeTool } from "./analyze.js";
|
|
15
15
|
import { attachTool } from "./attach.js";
|
|
16
16
|
import { captureTools, resetCaptures } from "./capture.js";
|
|
17
|
+
import { checkAttestorTool } from "./check-attestor.js";
|
|
17
18
|
import { listTabsTool } from "./eval.js";
|
|
18
19
|
import { howItWorksTool } from "./how-it-works.js";
|
|
19
20
|
import { liveViewTools } from "./live-view.js";
|
|
@@ -65,6 +66,7 @@ export function buildAgentTools(client, builderClient = client, externalState) {
|
|
|
65
66
|
tools.push(t);
|
|
66
67
|
}
|
|
67
68
|
tools.push(analyzeTool(state.captures));
|
|
69
|
+
tools.push(checkAttestorTool());
|
|
68
70
|
// navigate / eval_in_page / wait_for_page / get_cookies / list_requests /
|
|
69
71
|
// find_requests_containing / propose_provider / replay_request / run_proof —
|
|
70
72
|
// shared with the cloud loop, bound to each capture's local backend.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { checkAttestor, type CheckAttestorOptions } from '../../../proof/check-attestor.ts';
|
|
2
|
+
import { type RegisteredTool } from '../../server.ts';
|
|
3
|
+
/** `check_attestor`: proves a public request to show whether the attestor
|
|
4
|
+
* works from this machine. `check` is injectable so tests need no network. */
|
|
5
|
+
export declare function checkAttestorTool(check?: (opts: CheckAttestorOptions) => ReturnType<typeof checkAttestor>): RegisteredTool;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { checkAttestor, } from "../../../proof/check-attestor.js";
|
|
2
|
+
import { defineTool } from "../../server.js";
|
|
3
|
+
/** `check_attestor`: proves a public request to show whether the attestor
|
|
4
|
+
* works from this machine. `check` is injectable so tests need no network. */
|
|
5
|
+
export function checkAttestorTool(check = checkAttestor) {
|
|
6
|
+
return defineTool({
|
|
7
|
+
name: 'check_attestor',
|
|
8
|
+
description: 'Checks that the Reclaim attestor works from this machine by proving '
|
|
9
|
+
+ 'a public CoinGecko price request with a throwaway key. It uses no '
|
|
10
|
+
+ 'credential, provider, or logged-in site, and takes up to 45 '
|
|
11
|
+
+ 'seconds (about 8 when healthy). Call it once when you set up a '
|
|
12
|
+
+ 'new provider build, and after `run_proof` fails with a network '
|
|
13
|
+
+ 'error. Returns `{ ok, attestorUrl, durationMs, '
|
|
14
|
+
+ 'extractedParameters, error, errorKind }`. If `ok` is true, the '
|
|
15
|
+
+ 'attestor is fine and a failing proof points to the target site. '
|
|
16
|
+
+ 'If `errorKind` is `attestor-unreachable`, this machine cannot '
|
|
17
|
+
+ 'reach the attestor (network, VPN, firewall, or proxy): tell the '
|
|
18
|
+
+ 'developer. If it is `setup-failed`, the ZK file download failed '
|
|
19
|
+
+ 'before the attestor was contacted: tell the developer to check '
|
|
20
|
+
+ 'network access to the circuit download. If it is '
|
|
21
|
+
+ '`attestor-error`, the attestor rejected the claim: report '
|
|
22
|
+
+ '`error`.',
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
attestorUrl: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Attestor websocket URL. Set it only if the developer '
|
|
29
|
+
+ 'supplied one; the default is the public attestor.',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
annotations: { openWorldHint: true, readOnlyHint: true },
|
|
34
|
+
}, async ({ attestorUrl }) => {
|
|
35
|
+
return check(attestorUrl ? { attestorUrl } : {});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* attached container browser's built-in viewer behind a public URL, so someone
|
|
4
4
|
* who is not at this machine can watch it and optionally drive it.
|
|
5
5
|
*
|
|
6
|
-
* The server never runs, downloads or resolves cloudflared
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The server never runs, downloads or resolves cloudflared. The developer
|
|
7
|
+
* chooses who runs it: they run it themselves (recommended) or approve the
|
|
8
|
+
* agent running it. Either way the `trycloudflare.com` URL it prints comes
|
|
9
|
+
* back as `publicUrl` to get the link to hand out.
|
|
9
10
|
*
|
|
10
11
|
* Scoped to `container` mode on purpose. A `builder`-mode browser already has
|
|
11
12
|
* a hosted live view at `attach_browser`'s `liveViewUrl`.
|
|
@@ -24,9 +25,13 @@ export function liveViewTools(attachRef) {
|
|
|
24
25
|
description: 'Returns a link that lets someone who is not at this machine watch '
|
|
25
26
|
+ 'and drive the attached `container` browser, for example to sign '
|
|
26
27
|
+ 'in. This server opens no tunnel itself. Step 1: call with no '
|
|
27
|
-
+ 'arguments
|
|
28
|
-
+ 'command
|
|
29
|
-
+ '
|
|
28
|
+
+ 'arguments. It returns the local viewer URL, the `cloudflared` '
|
|
29
|
+
+ 'command, and two `choices`. Ask the developer to pick one before '
|
|
30
|
+
+ 'any tunnel runs: (a, recommended) they run `! cloudflared tunnel '
|
|
31
|
+
+ '--url http://localhost:<port>` themselves and paste the printed '
|
|
32
|
+
+ '`https://*.trycloudflare.com` URL; (b) they explicitly approve '
|
|
33
|
+
+ 'you running the command in the background. Never install '
|
|
34
|
+
+ '`cloudflared` without approval. Step 2: call again with the URL '
|
|
30
35
|
+ 'as `publicUrl` to get the link to relay. The link grants access '
|
|
31
36
|
+ 'to that browser session only, so share it only with the person '
|
|
32
37
|
+ 'who needs it. For `builder`, use the `liveViewUrl` from '
|
|
@@ -75,27 +80,46 @@ export function liveViewTools(attachRef) {
|
|
|
75
80
|
+ 'separate window is visible too.',
|
|
76
81
|
'Opening it on a phone lays the page out for that phone, with '
|
|
77
82
|
+ 'touch, pinch-zoom and the soft keyboard.',
|
|
78
|
-
'When they are done, stop the cloudflared process
|
|
79
|
-
+ '
|
|
83
|
+
'When they are done, stop the cloudflared process: yourself if '
|
|
84
|
+
+ 'you started it, otherwise ask the developer. The browser '
|
|
85
|
+
+ 'keeps running until `dispose_browser`.',
|
|
80
86
|
],
|
|
81
87
|
};
|
|
82
88
|
}
|
|
83
89
|
const localUrl = `http://localhost:${viewerPort}`;
|
|
90
|
+
const command = `cloudflared tunnel --url ${localUrl}`;
|
|
84
91
|
return {
|
|
85
92
|
localViewerUrl: ensureLiveViewUrl(localUrl),
|
|
86
93
|
port: viewerPort,
|
|
87
|
-
command
|
|
94
|
+
command,
|
|
95
|
+
choices: [
|
|
96
|
+
{
|
|
97
|
+
id: 'developer-runs',
|
|
98
|
+
recommended: true,
|
|
99
|
+
action: `Run \`! ${command}\` in the prompt, then paste the `
|
|
100
|
+
+ 'https://*.trycloudflare.com URL it prints.',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'agent-runs',
|
|
104
|
+
recommended: false,
|
|
105
|
+
action: 'Approve the agent to run the command in the '
|
|
106
|
+
+ 'background and read the URL from its output.',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
88
109
|
_notes: [
|
|
89
|
-
'
|
|
90
|
-
+ '
|
|
91
|
-
|
|
92
|
-
+ '
|
|
110
|
+
'Ask the developer to choose before any tunnel runs. Do not run '
|
|
111
|
+
+ '`command` until they pick `agent-runs`.',
|
|
112
|
+
'If they pick `developer-runs`, wait for the pasted URL. If they '
|
|
113
|
+
+ 'pick `agent-runs`, run `command` in a background process and '
|
|
114
|
+
+ 'read its output for a line containing '
|
|
115
|
+
+ '`https://<words>.trycloudflare.com`.',
|
|
116
|
+
'If `cloudflared` is not installed, tell the developer and ask '
|
|
117
|
+
+ 'them to install it '
|
|
118
|
+
+ '(https://developers.cloudflare.com/cloudflare-one/'
|
|
119
|
+
+ 'connections/connect-networks/downloads/). Never install it '
|
|
93
120
|
+ 'without their approval.',
|
|
94
|
-
'
|
|
95
|
-
+ '
|
|
96
|
-
'Call share_browser_view again with that value as `publicUrl` '
|
|
97
|
-
+ 'to get the link to relay. Never guess or reconstruct the '
|
|
98
|
-
+ 'hostname.',
|
|
121
|
+
'Call share_browser_view again with the URL as `publicUrl` to get '
|
|
122
|
+
+ 'the link to relay. Never guess or reconstruct the hostname.',
|
|
99
123
|
'Expose only this port. Never tunnel the browser\'s CDP port.',
|
|
100
124
|
],
|
|
101
125
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ProofResult } from '../../../proof/attestor.ts';
|
|
2
|
+
import { type AttestorCheckResult, type CheckAttestorOptions } from '../../../proof/check-attestor.ts';
|
|
2
3
|
/**
|
|
3
4
|
* The MCP's rich `run_proof` view: persist the full claim to disk, return
|
|
4
5
|
* identifier + extractedParameters + a prettified claim (request dropped, byte
|
|
@@ -11,3 +12,14 @@ export declare function shapeRichProofResult(result: ProofResult, opts: {
|
|
|
11
12
|
hasSecrets: boolean;
|
|
12
13
|
attestorUrl?: string;
|
|
13
14
|
}): Record<string, unknown>;
|
|
15
|
+
export interface ProofDiagnosis {
|
|
16
|
+
attestor: AttestorCheckResult;
|
|
17
|
+
likelyCause: string;
|
|
18
|
+
next: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* When an unverified proof failed with a network-like error, runs the
|
|
22
|
+
* attestor check once and says whether the attestor or the target site is the
|
|
23
|
+
* likely cause. Returns `undefined` for verified proofs and other errors.
|
|
24
|
+
*/
|
|
25
|
+
export declare function diagnoseNetworkFailure(result: Pick<ProofResult, 'verified' | 'error' | 'errorKind'>, check: (opts: CheckAttestorOptions) => Promise<AttestorCheckResult>, attestorUrl?: string): Promise<ProofDiagnosis | undefined>;
|
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { agentTraceEnabled } from "../../../consts.js";
|
|
4
4
|
import { reclaimHomeDirPath } from "../../../paths.js";
|
|
5
5
|
import { tailLogs, warnsAndErrors, } from "../../../proof/attestor.js";
|
|
6
|
+
import { isNetworkError, } from "../../../proof/check-attestor.js";
|
|
6
7
|
function claimsDir() {
|
|
7
8
|
return join(reclaimHomeDirPath(), 'claims');
|
|
8
9
|
}
|
|
@@ -157,3 +158,34 @@ export function shapeRichProofResult(result, opts) {
|
|
|
157
158
|
}
|
|
158
159
|
return out;
|
|
159
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* When an unverified proof failed with a network-like error, runs the
|
|
163
|
+
* attestor check once and says whether the attestor or the target site is the
|
|
164
|
+
* likely cause. Returns `undefined` for verified proofs and other errors.
|
|
165
|
+
*/
|
|
166
|
+
export async function diagnoseNetworkFailure(result, check, attestorUrl) {
|
|
167
|
+
if (result.verified
|
|
168
|
+
|| result.errorKind !== undefined
|
|
169
|
+
|| !isNetworkError(result.error)) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
const attestor = await check(attestorUrl ? { attestorUrl } : {});
|
|
173
|
+
if (attestor.ok) {
|
|
174
|
+
return {
|
|
175
|
+
attestor,
|
|
176
|
+
likelyCause: 'The attestor works from this machine, so the target site likely '
|
|
177
|
+
+ 'blocks or drops the attestor\'s network (geo or IP restriction).',
|
|
178
|
+
next: 'If the browser attach mode is "builder", call `run_proof_via_pod`. '
|
|
179
|
+
+ 'Otherwise tell the developer and suggest asking the Reclaim team. '
|
|
180
|
+
+ 'Do not retry the same proof.',
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
attestor,
|
|
185
|
+
likelyCause: 'The attestor check also failed, so this machine cannot reach the '
|
|
186
|
+
+ 'attestor (network, VPN, firewall, or proxy).',
|
|
187
|
+
next: 'Tell the developer. To see the connection details, suggest '
|
|
188
|
+
+ 'restarting the MCP server with `RECLAIM_AGENT_TRACE=1`. Do not '
|
|
189
|
+
+ 'retry the same proof.',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkAttestor } from '../../../proof/check-attestor.ts';
|
|
1
2
|
import type { RegisteredTool } from '../../server.ts';
|
|
2
3
|
import type { CaptureStore } from './capture.ts';
|
|
3
4
|
/** The shared authoring tools mapped to the MCP's shape: resolve each call's
|
|
@@ -5,4 +6,4 @@ import type { CaptureStore } from './capture.ts';
|
|
|
5
6
|
* takes `captureId`; `run_proof` also gets the credential-aware contract. The
|
|
6
7
|
* tools are authored plain in `../../../authoring`; this only wraps them with
|
|
7
8
|
* `extendTool` (handlers untouched — loose arg parse keeps `captureId`). */
|
|
8
|
-
export declare function sharedAuthoringTools(captures: CaptureStore): RegisteredTool[];
|
|
9
|
+
export declare function sharedAuthoringTools(captures: CaptureStore, check?: typeof checkAttestor): RegisteredTool[];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { authoringTools, extendTool, } from "../../../authoring/index.js";
|
|
2
2
|
import { runAttestorProof } from "../../../proof/attestor.js";
|
|
3
|
+
import { checkAttestor } from "../../../proof/check-attestor.js";
|
|
3
4
|
import { replayProviderInPage } from "../../../proof/page-replay.js";
|
|
4
|
-
import { shapeRichProofResult } from "./proof.js";
|
|
5
|
+
import { diagnoseNetworkFailure, shapeRichProofResult } from "./proof.js";
|
|
5
6
|
/**
|
|
6
7
|
* OPRF mode this LOCAL (non-TEE) attestor can actually run. Recipes carry
|
|
7
8
|
* `oprf-mpc` — the canonical/publish mode a TEE expects — but the local dev
|
|
@@ -32,7 +33,7 @@ function withLocalOprfMode(provider) {
|
|
|
32
33
|
/** The local backend for one capture: drive its tab over CDP, read its
|
|
33
34
|
* captured requests, prove via the in-process attestor, replay by re-fetching
|
|
34
35
|
* FROM INSIDE the tab (real cookies/session — see proof/page-replay.ts). */
|
|
35
|
-
function localBackend(entry, kept) {
|
|
36
|
+
function localBackend(entry, kept, check) {
|
|
36
37
|
const { tabClient, session, drafts } = entry;
|
|
37
38
|
const sendCdp = (method, params) => {
|
|
38
39
|
return tabClient.send(method, params ?? {});
|
|
@@ -76,11 +77,13 @@ function localBackend(entry, kept) {
|
|
|
76
77
|
...(opts?.ownerAddress ? { ownerAddress: opts.ownerAddress } : {}),
|
|
77
78
|
...(opts?.attestorUrl ? { attestorUrl: opts.attestorUrl } : {}),
|
|
78
79
|
});
|
|
79
|
-
|
|
80
|
+
const shaped = shapeRichProofResult(result, {
|
|
80
81
|
provider: draft.provider,
|
|
81
82
|
hasSecrets: Object.keys(draft.secrets).length > 0,
|
|
82
83
|
...(opts?.attestorUrl ? { attestorUrl: opts.attestorUrl } : {}),
|
|
83
84
|
});
|
|
85
|
+
const diagnosis = await diagnoseNetworkFailure(result, check, opts?.attestorUrl);
|
|
86
|
+
return diagnosis ? { ...shaped, diagnosis } : shaped;
|
|
84
87
|
},
|
|
85
88
|
};
|
|
86
89
|
}
|
|
@@ -88,25 +91,26 @@ function localBackend(entry, kept) {
|
|
|
88
91
|
* tool by the host below (the cloud's TEE signs in-enclave, so it needs none
|
|
89
92
|
* of this). */
|
|
90
93
|
const RUN_PROOF_EXTRAS = {
|
|
91
|
-
description: '
|
|
92
|
-
+ '
|
|
93
|
-
+ '
|
|
94
|
-
+ '
|
|
95
|
-
+ '
|
|
96
|
-
+ '
|
|
97
|
-
+ '
|
|
98
|
-
+ '
|
|
99
|
-
+ '`attestorUrl`
|
|
100
|
-
+ '
|
|
101
|
-
+ '
|
|
102
|
-
+ '
|
|
103
|
-
+ '
|
|
104
|
-
+ '
|
|
105
|
-
+ '
|
|
106
|
-
+ '
|
|
107
|
-
+ '
|
|
108
|
-
+
|
|
109
|
-
+ '
|
|
94
|
+
description: 'Proves the drafted recipe with a full zkTLS proof through the attestor '
|
|
95
|
+
+ 'SDK. Call it after `replay_request` looks right; attestor calls are '
|
|
96
|
+
+ 'expensive. Call `resolve_owner_key` first and pass its address as '
|
|
97
|
+
+ '`ownerAddress`. Writes the full claim to '
|
|
98
|
+
+ '`~/.reclaim/claims/<timestamp>.json`. Returns `identifier`, '
|
|
99
|
+
+ '`extractedParameters`, `claimPath`, and a readable `claim` (request '
|
|
100
|
+
+ 'removed, byte arrays as base64). Set `RECLAIM_AGENT_TRACE=1` to also '
|
|
101
|
+
+ 'get `runLogPath`, a diagnostic dump with the attestor log stream. '
|
|
102
|
+
+ 'Set `attestorUrl` only if the developer supplied one; never invent '
|
|
103
|
+
+ 'backup URLs. If `errorKind` is `missing-owner-key`, the address has '
|
|
104
|
+
+ 'no known private key: run `resolve_owner_key` again, or issue or '
|
|
105
|
+
+ 'import a credential. Unlike `replay_request`, which re-sends from '
|
|
106
|
+
+ 'inside the attached browser tab, the attestor calls the target from '
|
|
107
|
+
+ 'its own network and IP. A request that replayed can still fail here '
|
|
108
|
+
+ 'for geo- or IP-bound content, such as country-locked pages or '
|
|
109
|
+
+ 'IP-allowlisted APIs. That is a property of the site, not a draft '
|
|
110
|
+
+ 'bug. After a network-like failure ("connection closed", '
|
|
111
|
+
+ '`ERROR_NETWORK_ERROR`, timeout), the result includes `diagnosis` '
|
|
112
|
+
+ 'with `attestor` (a `check_attestor` result), `likelyCause`, and '
|
|
113
|
+
+ '`next`. Follow `next`; do not retry the same proof.',
|
|
110
114
|
properties: {
|
|
111
115
|
ownerAddress: {
|
|
112
116
|
type: 'string',
|
|
@@ -124,7 +128,7 @@ const RUN_PROOF_EXTRAS = {
|
|
|
124
128
|
* takes `captureId`; `run_proof` also gets the credential-aware contract. The
|
|
125
129
|
* tools are authored plain in `../../../authoring`; this only wraps them with
|
|
126
130
|
* `extendTool` (handlers untouched — loose arg parse keeps `captureId`). */
|
|
127
|
-
export function sharedAuthoringTools(captures) {
|
|
131
|
+
export function sharedAuthoringTools(captures, check = checkAttestor) {
|
|
128
132
|
const resolveBackend = (args) => {
|
|
129
133
|
const captureId = String(args.captureId);
|
|
130
134
|
const entry = captures.sessions.get(captureId);
|
|
@@ -132,7 +136,7 @@ export function sharedAuthoringTools(captures) {
|
|
|
132
136
|
throw new Error(`no capture ${captureId} — call start_capture first `
|
|
133
137
|
+ '(it has been reset or never started).');
|
|
134
138
|
}
|
|
135
|
-
return localBackend(entry, captures);
|
|
139
|
+
return localBackend(entry, captures, check);
|
|
136
140
|
};
|
|
137
141
|
return authoringTools(resolveBackend).map((tool) => {
|
|
138
142
|
if (tool.definition.name === 'run_proof') {
|
|
@@ -10,10 +10,11 @@ export function startIntegrationTool() {
|
|
|
10
10
|
return defineTool({
|
|
11
11
|
name: 'start_integration',
|
|
12
12
|
description: 'Returns a step-by-step plan for adding Reclaim verification to the '
|
|
13
|
-
+ 'current codebase: sign in,
|
|
14
|
-
+ '
|
|
15
|
-
+ '
|
|
16
|
-
+ '
|
|
13
|
+
+ 'current codebase: sign in, set up the app (session route, callback, '
|
|
14
|
+
+ 'Verify button, local event proxy) in parallel with building or '
|
|
15
|
+
+ 'choosing the provider, then wire the provider and test. Call it '
|
|
16
|
+
+ 'first when a developer wants their users to prove something, and '
|
|
17
|
+
+ 'follow the plan to the end. Changes nothing itself.',
|
|
17
18
|
inputSchema: {
|
|
18
19
|
type: 'object',
|
|
19
20
|
required: ['goal'],
|
|
@@ -4,7 +4,9 @@ import type { RegisteredTool } from '../../server.ts';
|
|
|
4
4
|
* generated API surface. `get_verification_result` authenticates with the org
|
|
5
5
|
* token, checks status, pulls the stored payload, and decrypts it locally with
|
|
6
6
|
* the optional eth key — a flow the generated raw-API tools can't express
|
|
7
|
-
* (the decryption key never leaves the consumer).
|
|
7
|
+
* (the decryption key never leaves the consumer). `read_callback_events` and
|
|
8
|
+
* `proxy_callback_events` read and forward the org's pull-listener events.
|
|
9
|
+
* Diagnosis is left to the
|
|
8
10
|
* agent over the generated tools (`get_verification_session`,
|
|
9
11
|
* `list_verification_events`, `list_verification_deliveries`), whose
|
|
10
12
|
* descriptions carry the lifecycle and delivery semantics.
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
+
import { proxyCallbackEventsTool } from "./proxy-callback-events.js";
|
|
2
|
+
import { readCallbackEventsTool } from "./read-callback-events.js";
|
|
1
3
|
import { verificationResultTool } from "./result.js";
|
|
2
4
|
/**
|
|
3
5
|
* Builder-mode verification tools: higher-level orchestrations over the
|
|
4
6
|
* generated API surface. `get_verification_result` authenticates with the org
|
|
5
7
|
* token, checks status, pulls the stored payload, and decrypts it locally with
|
|
6
8
|
* the optional eth key — a flow the generated raw-API tools can't express
|
|
7
|
-
* (the decryption key never leaves the consumer).
|
|
9
|
+
* (the decryption key never leaves the consumer). `read_callback_events` and
|
|
10
|
+
* `proxy_callback_events` read and forward the org's pull-listener events.
|
|
11
|
+
* Diagnosis is left to the
|
|
8
12
|
* agent over the generated tools (`get_verification_session`,
|
|
9
13
|
* `list_verification_events`, `list_verification_deliveries`), whose
|
|
10
14
|
* descriptions carry the lifecycle and delivery semantics.
|
|
11
15
|
*/
|
|
12
16
|
export function buildVerificationTools() {
|
|
13
|
-
return [
|
|
17
|
+
return [
|
|
18
|
+
verificationResultTool(),
|
|
19
|
+
readCallbackEventsTool(),
|
|
20
|
+
proxyCallbackEventsTool(),
|
|
21
|
+
];
|
|
14
22
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ReclaimClient } from '@reclaimprotocol/client/api';
|
|
2
|
+
import type { IOperationResponse } from '@reclaimprotocol/client/openapi';
|
|
3
|
+
/** Runtime seams. Production passes none; tests pass a stub `fetch`. */
|
|
4
|
+
export interface CallbackDeps {
|
|
5
|
+
fetch?: typeof globalThis.fetch;
|
|
6
|
+
/** Waits between heartbeats and polls. Resolves, without error, on abort. */
|
|
7
|
+
sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
8
|
+
keeper?: ListenerKeeper;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Events the org's pull listener subscribes to: the five session-ending
|
|
12
|
+
* events (the first four carry the signed result; `session_expired` has no
|
|
13
|
+
* result) plus the lifecycle milestones an app developer checks while testing.
|
|
14
|
+
* High-volume diagnostics (`network_request_observed`, `provider_script_log`,
|
|
15
|
+
* `user_interaction_*`) are left out, so a forwarded session sends the app at
|
|
16
|
+
* most 11 requests instead of dozens.
|
|
17
|
+
*/
|
|
18
|
+
export declare const LISTENER_EVENTS: readonly ["verification_success", "verification_rejected", "verification_error", "verification_cancelled", "session_expired", "session_pending", "verification_client_opened", "authenticated", "verification_proofs_completed", "verification_result_submitting", "verification_result_submission_failed"];
|
|
19
|
+
export type ListenerInfo = {
|
|
20
|
+
subscriptionId: string;
|
|
21
|
+
created: boolean;
|
|
22
|
+
/** When the lease lapses unless the heartbeat renews it. */
|
|
23
|
+
expiresAt: string;
|
|
24
|
+
};
|
|
25
|
+
export type CallbackEvent = IOperationResponse<'ListCallbackEvents'>['items'][number];
|
|
26
|
+
/**
|
|
27
|
+
* A client that presents the organization's API key. With a `signal`, every
|
|
28
|
+
* request aborts when it does.
|
|
29
|
+
*/
|
|
30
|
+
export declare function orgClient(secret: string, deps?: CallbackDeps, signal?: AbortSignal): ReclaimClient;
|
|
31
|
+
/**
|
|
32
|
+
* Keeps each org's pull listener alive while this MCP process runs. Creating
|
|
33
|
+
* the subscription is the heartbeat: it renews a lease, and the listener
|
|
34
|
+
* stops receiving events when the lease lapses. {@link release} ends the
|
|
35
|
+
* lease at shutdown.
|
|
36
|
+
*/
|
|
37
|
+
export declare class ListenerKeeper {
|
|
38
|
+
#private;
|
|
39
|
+
/** Creates or renews the listener and starts its heartbeat (once). */
|
|
40
|
+
ensure(secret: string, deps?: CallbackDeps): Promise<ListenerInfo>;
|
|
41
|
+
/** Ends every lease and stops the heartbeats, within a short timeout. */
|
|
42
|
+
release(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/** Releases every listener this process keeps alive. Runs at MCP shutdown. */
|
|
45
|
+
export declare function releaseAllListeners(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Create the org's pull listener and keep it alive until shutdown. An org has
|
|
48
|
+
* one, so a second call returns the existing subscription (HTTP 200).
|
|
49
|
+
*/
|
|
50
|
+
export declare function ensureListener(secret: string, deps?: CallbackDeps): Promise<ListenerInfo>;
|
|
51
|
+
export declare function sleepFor(ms: number, signal: AbortSignal): Promise<void>;
|