@reclaimprotocol/agent 0.1.40 → 0.1.42
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 +32 -0
- package/lib/authoring/tools.js +4 -1
- package/lib/config/store.d.ts +7 -0
- package/lib/consts.d.ts +5 -0
- package/lib/consts.js +10 -0
- package/lib/mcp/api/operations.gen.js +6 -2
- package/lib/mcp/api/tools.js +4 -0
- package/lib/mcp/server.js +6 -1
- package/lib/mcp/start.js +27 -1
- package/lib/mcp/tools/agent/proof.js +4 -2
- package/lib/mcp/tools/agent/propose.js +7 -0
- package/lib/mcp/tools/agent/publish.js +4 -1
- package/lib/mcp/tools/agent/replay.js +3 -1
- package/lib/mcp/tools/agent/start-integration.d.ts +2 -0
- package/lib/mcp/tools/agent/start-integration.js +7 -0
- package/lib/mcp/tools/authenticate/authenticate.js +3 -0
- package/lib/telemetry/index.d.ts +9 -0
- package/lib/telemetry/index.js +13 -0
- package/lib/telemetry/telemetry.d.ts +54 -0
- package/lib/telemetry/telemetry.js +175 -0
- package/lib/telemetry/tool-call.d.ts +24 -0
- package/lib/telemetry/tool-call.js +102 -0
- package/package.json +2 -2
- package/skill/SKILL.builder.md +5 -0
- package/skill/SKILL.common.md +34 -3
- package/skill/SKILL.old.md +5 -0
- package/skill/builder-mode.md +1 -0
- package/skill/old-mode.md +1 -0
- package/src/authoring/tools.ts +4 -1
- package/src/config/store.ts +4 -0
- package/src/consts.ts +11 -0
- package/src/mcp/api/operations.gen.ts +6 -2
- package/src/mcp/api/tools.ts +4 -0
- package/src/mcp/server.ts +6 -1
- package/src/mcp/start.ts +34 -0
- package/src/mcp/tools/agent/proof.ts +4 -2
- package/src/mcp/tools/agent/propose.ts +8 -0
- package/src/mcp/tools/agent/publish.ts +4 -1
- package/src/mcp/tools/agent/replay.ts +3 -1
- package/src/mcp/tools/agent/start-integration.ts +10 -0
- package/src/mcp/tools/authenticate/authenticate.ts +3 -0
- package/src/telemetry/index.ts +18 -0
- package/src/telemetry/telemetry.ts +206 -0
- package/src/telemetry/tool-call.ts +115 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { isProblemError } from '@reclaimprotocol/client/api';
|
|
2
|
+
import { currentTelemetry } from "./index.js";
|
|
3
|
+
const GOAL_MAX = 300;
|
|
4
|
+
const USER_REQUEST_MAX = 500;
|
|
5
|
+
const DOMAIN_MAX = 200;
|
|
6
|
+
const DATA_POINTS_MAX = 20;
|
|
7
|
+
const DATA_POINT_MAX = 200;
|
|
8
|
+
/**
|
|
9
|
+
* Times a tool call and records it. Call `end()` once when the call
|
|
10
|
+
* finishes. It never throws, so it can't change the outcome of the call.
|
|
11
|
+
*/
|
|
12
|
+
export function startToolCall(tool, args) {
|
|
13
|
+
const startedAt = Date.now();
|
|
14
|
+
return {
|
|
15
|
+
end(err) {
|
|
16
|
+
try {
|
|
17
|
+
const telemetry = currentTelemetry();
|
|
18
|
+
if (!telemetry) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const input = allowlistedInput(tool, args);
|
|
22
|
+
telemetry.track('tool_call', {
|
|
23
|
+
tool,
|
|
24
|
+
ok: err === undefined,
|
|
25
|
+
...(err === undefined ? {} : { errorCode: errorCode(err) }),
|
|
26
|
+
durationMs: Date.now() - startedAt,
|
|
27
|
+
mode: telemetry.mode,
|
|
28
|
+
...(input ? { input } : {}),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Telemetry must never affect a tool call.
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A category for the error, such as `http_401`. It's never the message,
|
|
39
|
+
* because messages can contain URLs, tokens, or file paths.
|
|
40
|
+
*/
|
|
41
|
+
export function errorCode(err) {
|
|
42
|
+
if (isProblemError(err)) {
|
|
43
|
+
return `http_${err.output.statusCode}`;
|
|
44
|
+
}
|
|
45
|
+
if (isRecord(err) && err.isBoom === true && isRecord(err.output)) {
|
|
46
|
+
return `http_${String(err.output.statusCode)}`;
|
|
47
|
+
}
|
|
48
|
+
if (err instanceof Error) {
|
|
49
|
+
if (err.name === 'ZodError') {
|
|
50
|
+
return 'invalid_input';
|
|
51
|
+
}
|
|
52
|
+
if (err.name === 'TimeoutError' || err.name === 'AbortError') {
|
|
53
|
+
return 'timeout';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return 'error';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The only arguments Reclaim records, and only for these tools. They are the
|
|
60
|
+
* developer's own description of what they want to build, which tells us
|
|
61
|
+
* which providers to add. Every other argument is dropped here, so a new tool
|
|
62
|
+
* records nothing until you add it to this list on purpose.
|
|
63
|
+
*/
|
|
64
|
+
export function allowlistedInput(tool, args) {
|
|
65
|
+
if (!isRecord(args)) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
switch (tool) {
|
|
69
|
+
case 'start_integration':
|
|
70
|
+
return compact({
|
|
71
|
+
goal: cut(args.goal, GOAL_MAX),
|
|
72
|
+
userRequest: cut(args.userRequest, USER_REQUEST_MAX),
|
|
73
|
+
});
|
|
74
|
+
case 'propose_provider':
|
|
75
|
+
return compact({ userRequest: cut(args.userRequest, USER_REQUEST_MAX) });
|
|
76
|
+
case 'discover_resolve': {
|
|
77
|
+
const query = isRecord(args.query) ? args.query : {};
|
|
78
|
+
const dataPoints = Array.isArray(query.dataPoints)
|
|
79
|
+
? query.dataPoints
|
|
80
|
+
.slice(0, DATA_POINTS_MAX)
|
|
81
|
+
.map((p) => cut(p, DATA_POINT_MAX))
|
|
82
|
+
.filter((p) => p !== undefined)
|
|
83
|
+
: undefined;
|
|
84
|
+
return compact({
|
|
85
|
+
domain: cut(query.domain, DOMAIN_MAX),
|
|
86
|
+
dataPoints: dataPoints?.length ? dataPoints : undefined,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
default:
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function cut(value, max) {
|
|
94
|
+
return typeof value === 'string' && value ? value.slice(0, max) : undefined;
|
|
95
|
+
}
|
|
96
|
+
/** Returns the object, or undefined when every field is undefined. */
|
|
97
|
+
function compact(obj) {
|
|
98
|
+
return Object.values(obj).some((v) => v !== undefined) ? obj : undefined;
|
|
99
|
+
}
|
|
100
|
+
function isRecord(value) {
|
|
101
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
102
|
+
}
|
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.42",
|
|
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",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"@noble/hashes": "^2.2.0",
|
|
85
85
|
"@puppeteer/browsers": "3.2.4",
|
|
86
86
|
"@reclaimprotocol/attestor-core": "^5.1.6",
|
|
87
|
-
"@reclaimprotocol/client": "0.1.
|
|
87
|
+
"@reclaimprotocol/client": "0.1.42",
|
|
88
88
|
"@reclaimprotocol/tls": "^0.1.3",
|
|
89
89
|
"chrome-remote-interface": "^0.33.0",
|
|
90
90
|
"domhandler": "^6.0.0",
|
package/skill/SKILL.builder.md
CHANGED
|
@@ -46,6 +46,11 @@ or asynchronous question tool for a blocking question.
|
|
|
46
46
|
`webSettings.jsUserScripts`) → `publish_provider_version` or
|
|
47
47
|
`submit_provider_version_for_review`. → topic `publish`
|
|
48
48
|
|
|
49
|
+
Replay or proof blocked with 403 after one retry and one correction? Stop
|
|
50
|
+
retrying and go straight to step 11: publish the draft unproven from the
|
|
51
|
+
captured request, then ask the developer to try a real verification. → topic
|
|
52
|
+
`prove`, "publish from the capture".
|
|
53
|
+
|
|
49
54
|
Data points on different requests? Run steps 7-10 once per request, then
|
|
50
55
|
publish every proven draft in one call. See topic `publish`.
|
|
51
56
|
|
package/skill/SKILL.common.md
CHANGED
|
@@ -409,9 +409,37 @@ supplies are context params.
|
|
|
409
409
|
often after `replay_request` succeeded): the site blocks clients that
|
|
410
410
|
aren't a real browser (bot protection or TLS fingerprint). Copying
|
|
411
411
|
headers or cookies won't help, and `run_proof_via_pod` doesn't change the
|
|
412
|
-
TLS fingerprint.
|
|
413
|
-
|
|
414
|
-
|
|
412
|
+
TLS fingerprint. Run `check_matchers` first so a recipe bug isn't
|
|
413
|
+
mistaken for blocking, then try ONE other endpoint that returns the value
|
|
414
|
+
(a JSON API the page calls). Blocked there too → publish from the
|
|
415
|
+
capture, below.
|
|
416
|
+
|
|
417
|
+
**Replay or proof keeps returning 403 (or another block): publish from
|
|
418
|
+
the capture.** A 403 from `replay_request` or `run_proof` says the site
|
|
419
|
+
defends against the authoring path. It does not say the recipe is wrong,
|
|
420
|
+
and a real verification runs inside the end user's own browser session,
|
|
421
|
+
which those defenses usually let through. Retry budget: one retry of the
|
|
422
|
+
same call, one correction (another endpoint, a `geoLocation` pin, or a
|
|
423
|
+
matcher fix via `check_matchers`), and one retry after the correction.
|
|
424
|
+
Past that, STOP. Don't re-capture, re-propose, or re-prove the same
|
|
425
|
+
request, and don't wait for the developer to tell you to stop. Instead:
|
|
426
|
+
|
|
427
|
+
1. Say plainly that replay/proof is blocked and that you are publishing
|
|
428
|
+
the version UNPROVEN, from the request you intercepted. Never say it
|
|
429
|
+
was proven.
|
|
430
|
+
2. Publish the draft as topic `publish` says
|
|
431
|
+
(`create_provider_version_from_capture`, then go live). The draft
|
|
432
|
+
holds the intercepted request, selectors, and params; that is all the
|
|
433
|
+
version needs.
|
|
434
|
+
3. Ask the developer to try it with a real verification. Builder mode:
|
|
435
|
+
`create_verification_session` and put its URL in the message. Legacy
|
|
436
|
+
devtools mode: the "try it" option in topic `publish`. End your turn
|
|
437
|
+
there.
|
|
438
|
+
4. When they return, check the session the usual way
|
|
439
|
+
(`get_verification_session` / `list_verification_events`, or the
|
|
440
|
+
devtools equivalent). Passed → done; it was only the authoring path
|
|
441
|
+
that was blocked. Failed with the same block → the site blocks
|
|
442
|
+
verification too; say so and look for a different endpoint.
|
|
415
443
|
|
|
416
444
|
**Do not auto-retry with a different `attestorUrl`.** There is one live
|
|
417
445
|
public attestor; backup hostnames (`attestor-2.*`, and so on) **do not
|
|
@@ -431,6 +459,9 @@ supplies are context params.
|
|
|
431
459
|
never repeat them.
|
|
432
460
|
- Never call `run_proof` before `replay_request` succeeds. Attestor calls are
|
|
433
461
|
expensive.
|
|
462
|
+
- Never loop a blocked replay or proof. A 403 that survives the retry budget
|
|
463
|
+
in `prove` means publish from the capture, unproven, and hand the developer
|
|
464
|
+
a real verification to try.
|
|
434
465
|
- Never pretend a proof succeeded when `verified: false`. Show the error and
|
|
435
466
|
iterate.
|
|
436
467
|
- Never write a literal value into `responseMatches.value` — that bakes the
|
package/skill/SKILL.old.md
CHANGED
|
@@ -28,6 +28,11 @@ topic before a step you aren't already fluent in.
|
|
|
28
28
|
→ topics `prove`, `credentials`
|
|
29
29
|
- [ ] 10. `create_provider_version_from_capture`. → topic `publish`
|
|
30
30
|
|
|
31
|
+
Replay or proof blocked with 403 after one retry and one correction? Stop
|
|
32
|
+
retrying and go straight to step 10: publish the draft unproven from the
|
|
33
|
+
captured request, then ask the developer to try a real verification. → topic
|
|
34
|
+
`prove`, "publish from the capture".
|
|
35
|
+
|
|
31
36
|
Multi-request provider? Run steps 6–9 once per request, then publish every
|
|
32
37
|
proven draft in one call. See topic `publish`.
|
|
33
38
|
|
package/skill/builder-mode.md
CHANGED
|
@@ -16,6 +16,7 @@ Provider-authoring and verification tools for the Builder backend.
|
|
|
16
16
|
- Call `attach_browser()` with no arguments. Don't ask which browser to use.
|
|
17
17
|
- Propose only the data points the claim needs.
|
|
18
18
|
- Provider versions are immutable: every change creates a new version.
|
|
19
|
+
- A 403 (or similar block) from `replay_request` or `run_proof` that survives one retry and one correction is not a reason to keep retrying. Publish the version from the captured request, say it is unproven, and ask the developer to try a real verification (topic `prove`).
|
|
19
20
|
|
|
20
21
|
## Asking the developer
|
|
21
22
|
|
package/skill/old-mode.md
CHANGED
|
@@ -9,6 +9,7 @@ Provider-authoring tools for the old devtools backend. The tool surface is reduc
|
|
|
9
9
|
- A provider found with `get_me_providers` or `get_provider_info` is published and usable as-is. Don't capture, replay, or prove it unless the developer asks.
|
|
10
10
|
- Before using a provider, found or just created, ask once how they want to use it (try it, integrate it, or build a new app), unless they already said. Topic `publish` has the options.
|
|
11
11
|
- Call `attach_browser()` with no arguments. Don't ask which browser to use.
|
|
12
|
+
- A 403 (or similar block) from `replay_request` or `run_proof` that survives one retry and one correction is not a reason to keep retrying. Publish the version from the captured request, say it is unproven, and ask the developer to try it (topic `prove`).
|
|
12
13
|
|
|
13
14
|
## Asking the developer
|
|
14
15
|
|
package/src/authoring/tools.ts
CHANGED
|
@@ -521,7 +521,10 @@ export function authoringTools(
|
|
|
521
521
|
+ 'of the site, not a bug in the draft. HTTP 403/406/429/503 or a '
|
|
522
522
|
+ 'challenge page from the attestor while the replay succeeded '
|
|
523
523
|
+ 'means the site blocks non-browser clients: copying headers or '
|
|
524
|
-
+ 'cookies will not help
|
|
524
|
+
+ 'cookies will not help. Try one other endpoint; if that is '
|
|
525
|
+
+ 'blocked too, publish the version from the capture and have '
|
|
526
|
+
+ 'the developer try a real verification '
|
|
527
|
+
+ '(how_it_works({ topic: "prove" })). Do not keep retrying.',
|
|
525
528
|
inputSchema: objectSchema({ draftId: { type: 'string' } }, ['draftId']),
|
|
526
529
|
annotations: { openWorldHint: true, destructiveHint: false },
|
|
527
530
|
},
|
package/src/config/store.ts
CHANGED
|
@@ -27,6 +27,9 @@ export interface ReclaimAgentConfig {
|
|
|
27
27
|
* - `old_devtools` — externally-supplied identity for OLD-devtools mode
|
|
28
28
|
* (USE_OLD_DEVTOOLS); independent of `session`'s
|
|
29
29
|
* builder token. Deletable with the rest of `src/old/`.
|
|
30
|
+
* - `analytics` — the random, anonymous client ID the usage data is
|
|
31
|
+
* sent under (see telemetry/telemetry.ts); holds no
|
|
32
|
+
* personal data
|
|
30
33
|
* - `org_keys` — organization API keys saved by `issue_org_token`, so
|
|
31
34
|
* the org-key tools work without the key in a transcript
|
|
32
35
|
* Sections are persisted independently via `ConfigStore.write`, which
|
|
@@ -43,6 +46,7 @@ export interface ReclaimConfig {
|
|
|
43
46
|
session?: CachedSession
|
|
44
47
|
old_devtools?: OldIdentity
|
|
45
48
|
org_keys?: SavedOrgKeys
|
|
49
|
+
analytics?: { clientId: string, createdAt: string }
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
/** Organization API keys (`rorg_…`) saved by `issue_org_token`. */
|
package/src/consts.ts
CHANGED
|
@@ -143,6 +143,17 @@ export function agentDisabled(): boolean {
|
|
|
143
143
|
return process.env.RECLAIM_AGENT_DISABLED === '1'
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Whether the server may send anonymous usage data. Off when you set
|
|
148
|
+
* `RECLAIM_TELEMETRY=0` or the cross-tool `DO_NOT_TRACK=1` convention.
|
|
149
|
+
*/
|
|
150
|
+
export function telemetryEnabled(): boolean {
|
|
151
|
+
const doNotTrack = process.env.DO_NOT_TRACK?.toLowerCase()
|
|
152
|
+
return process.env.RECLAIM_TELEMETRY !== '0'
|
|
153
|
+
&& doNotTrack !== '1'
|
|
154
|
+
&& doNotTrack !== 'true'
|
|
155
|
+
}
|
|
156
|
+
|
|
146
157
|
/** Old-devtools backend base URL. */
|
|
147
158
|
export function oldApiUrl(): string {
|
|
148
159
|
return process.env.RECLAIM_OLD_API_URL || DEFAULT_OLD_API_URL
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AUTO-GENERATED by packages/app/scripts/mcp-tools-gen.ts
|
|
2
|
-
// from packages/app/openapi.yaml (spec v0.1.0, source sha256:
|
|
2
|
+
// from packages/app/openapi.yaml (spec v0.1.0, source sha256:c3421d6bcf76).
|
|
3
3
|
// The hash catches drift the version misses (a spec can change without a
|
|
4
4
|
// version bump) — do not remove it as "redundant" with the version.
|
|
5
5
|
// Run `npm run openapi:gen --workspace=packages/app` to regenerate. Do not edit by hand.
|
|
@@ -123,7 +123,11 @@ export const MCP_TOOLS = {
|
|
|
123
123
|
"enum": [
|
|
124
124
|
"providers",
|
|
125
125
|
"revenue",
|
|
126
|
-
"usage"
|
|
126
|
+
"usage",
|
|
127
|
+
"goals",
|
|
128
|
+
"tools",
|
|
129
|
+
"agents",
|
|
130
|
+
"funnel"
|
|
127
131
|
]
|
|
128
132
|
}
|
|
129
133
|
}
|
package/src/mcp/api/tools.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
} from '@reclaimprotocol/client/openapi'
|
|
8
8
|
import assert from 'node:assert'
|
|
9
9
|
import { orgSecret } from '../../consts.ts'
|
|
10
|
+
import { startToolCall } from '../../telemetry/index.ts'
|
|
10
11
|
import { remapErrorAsResponse } from '../remap_errors.ts'
|
|
11
12
|
import { successResponse } from '../success-response.ts'
|
|
12
13
|
import { savedOrgKey } from '../tools/authenticate/org-key-store.ts'
|
|
@@ -68,6 +69,7 @@ export function registerTools(
|
|
|
68
69
|
toSnakeCase(opId),
|
|
69
70
|
shape,
|
|
70
71
|
async(input: unknown) => {
|
|
72
|
+
const call = startToolCall(toSnakeCase(opId), input)
|
|
71
73
|
try {
|
|
72
74
|
if(!isRecord(input)) {
|
|
73
75
|
throw new Error(`Invalid generated MCP arguments for ${opId}`)
|
|
@@ -85,8 +87,10 @@ export function registerTools(
|
|
|
85
87
|
opId,
|
|
86
88
|
args,
|
|
87
89
|
)
|
|
90
|
+
call.end()
|
|
88
91
|
return successResponse(result.data)
|
|
89
92
|
} catch(err) {
|
|
93
|
+
call.end(err)
|
|
90
94
|
return remapErrorAsResponse(err)
|
|
91
95
|
}
|
|
92
96
|
},
|
package/src/mcp/server.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import { startToolCall } from '../telemetry/index.ts'
|
|
2
3
|
import { type RegisteredTool } from '../tools/registry.ts'
|
|
3
4
|
import { toZod } from './api/to-zod.ts'
|
|
4
5
|
import { remapErrorAsResponse } from './remap_errors.ts'
|
|
@@ -31,9 +32,13 @@ export function registerToolList(server: McpServer, tools: RegisteredTool[]) {
|
|
|
31
32
|
: {}),
|
|
32
33
|
},
|
|
33
34
|
async(args) => {
|
|
35
|
+
const call = startToolCall(tool.definition.name, args)
|
|
34
36
|
try {
|
|
35
|
-
|
|
37
|
+
const result = successResponse(await tool.handler(args))
|
|
38
|
+
call.end()
|
|
39
|
+
return result
|
|
36
40
|
} catch(err) {
|
|
41
|
+
call.end(err)
|
|
37
42
|
return remapErrorAsResponse(err)
|
|
38
43
|
}
|
|
39
44
|
},
|
package/src/mcp/start.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
oldLogsUrl,
|
|
11
11
|
resolveOldMode,
|
|
12
12
|
sdkApiUrl,
|
|
13
|
+
telemetryEnabled,
|
|
13
14
|
} from '../consts.ts'
|
|
14
15
|
import { LOGGER } from '../logger.ts'
|
|
15
16
|
import { ReclaimOldClient } from '../old/client.ts'
|
|
@@ -19,6 +20,11 @@ import { ReclaimSdkClient } from '../old/sdk-client.ts'
|
|
|
19
20
|
import { buildOldBackendTools } from '../old/tools/build.ts'
|
|
20
21
|
import { NAME, VERSION } from '../pkg.ts'
|
|
21
22
|
import { startZkFilesDownload } from '../proof/zk-files.ts'
|
|
23
|
+
import {
|
|
24
|
+
currentTelemetry,
|
|
25
|
+
installTelemetry,
|
|
26
|
+
Telemetry
|
|
27
|
+
} from '../telemetry/index.ts'
|
|
22
28
|
import { registerTools } from './api/tools.ts'
|
|
23
29
|
import {
|
|
24
30
|
type AgentState,
|
|
@@ -78,6 +84,13 @@ export async function startMcpServer() {
|
|
|
78
84
|
}),
|
|
79
85
|
headers: { 'User-Agent': `${NAME}/${VERSION}` },
|
|
80
86
|
})
|
|
87
|
+
// Anonymous usage data. Installed before any tool registers so every tool
|
|
88
|
+
// call is covered. When the developer opts out, nothing is created.
|
|
89
|
+
const mode = oldMode ? 'devtools' : 'builder'
|
|
90
|
+
if(telemetryEnabled()) {
|
|
91
|
+
installTelemetry(new Telemetry({ client: builderClient, mode }))
|
|
92
|
+
}
|
|
93
|
+
|
|
81
94
|
// Backend-mode toggle — available in both modes and regardless of whether
|
|
82
95
|
// the agent tools are enabled, so a user can always switch either direction.
|
|
83
96
|
registerToolList(server, buildModeTools(config, oldMode))
|
|
@@ -151,6 +164,19 @@ export async function startMcpServer() {
|
|
|
151
164
|
installShutdownHooks(() => agentState)
|
|
152
165
|
}
|
|
153
166
|
|
|
167
|
+
// The client's name and version arrive in the `initialize` handshake, so
|
|
168
|
+
// the session starts only after it completes.
|
|
169
|
+
server.server.oninitialized = () => {
|
|
170
|
+
const telemetry = currentTelemetry()
|
|
171
|
+
telemetry?.setAgent(server.server.getClientVersion())
|
|
172
|
+
telemetry?.track('session_start', {
|
|
173
|
+
mode,
|
|
174
|
+
agentTools: agentToolsEnabled,
|
|
175
|
+
os: knownOs(process.platform),
|
|
176
|
+
nodeVersion: process.versions.node,
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
154
180
|
const transport = new StdioServerTransport()
|
|
155
181
|
await server.connect(transport)
|
|
156
182
|
|
|
@@ -192,6 +218,7 @@ function installShutdownHooks(state: () => AgentState | undefined): void {
|
|
|
192
218
|
void Promise.all([
|
|
193
219
|
stopAllForwarders(),
|
|
194
220
|
releaseAllListeners(),
|
|
221
|
+
currentTelemetry()?.close(),
|
|
195
222
|
current && disposeAgentState(current),
|
|
196
223
|
])
|
|
197
224
|
.catch((err: unknown) => {
|
|
@@ -209,3 +236,10 @@ function installShutdownHooks(state: () => AgentState | undefined): void {
|
|
|
209
236
|
process.stdin.once('end', () => shutdown('stdin ended'))
|
|
210
237
|
process.stdin.once('close', () => shutdown('stdin closed'))
|
|
211
238
|
}
|
|
239
|
+
|
|
240
|
+
/** Maps the platform to the values the analytics API accepts. */
|
|
241
|
+
function knownOs(platform: NodeJS.Platform) {
|
|
242
|
+
return platform === 'darwin' || platform === 'linux' || platform === 'win32'
|
|
243
|
+
? platform
|
|
244
|
+
: 'other'
|
|
245
|
+
}
|
|
@@ -286,8 +286,10 @@ export function diagnoseBlockedClient(
|
|
|
286
286
|
+ 'published version goes out from there too. Otherwise, try a '
|
|
287
287
|
+ 'different endpoint that returns the value, such as a JSON API the '
|
|
288
288
|
+ 'page calls (find_requests_containing), then propose and prove '
|
|
289
|
-
+ 'that. If none works, stop
|
|
290
|
-
+ '
|
|
289
|
+
+ 'that. If none works, stop retrying: publish the version from the '
|
|
290
|
+
+ 'capture, unproven, and ask the developer to try a real '
|
|
291
|
+
+ 'verification (how_it_works({ topic: "prove" })). Do not tweak '
|
|
292
|
+
+ 'headers.',
|
|
291
293
|
}
|
|
292
294
|
}
|
|
293
295
|
|
|
@@ -28,6 +28,14 @@ export function proposeTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
28
28
|
{
|
|
29
29
|
requestId: { type: 'string' },
|
|
30
30
|
name: { type: 'string' },
|
|
31
|
+
userRequest: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
maxLength: 500,
|
|
34
|
+
description:
|
|
35
|
+
'The developer\'s request in their own words, as they wrote it. '
|
|
36
|
+
+ 'Reclaim records this text to improve the product, so leave '
|
|
37
|
+
+ 'out secrets and personal data.',
|
|
38
|
+
},
|
|
31
39
|
shortenNames: {
|
|
32
40
|
type: 'boolean',
|
|
33
41
|
description:
|
|
@@ -239,7 +239,10 @@ export function createVersionFromCaptureTool(
|
|
|
239
239
|
{
|
|
240
240
|
name: 'create_provider_version_from_capture',
|
|
241
241
|
description:
|
|
242
|
-
'Creates a new immutable provider version after `run_proof` succeeds
|
|
242
|
+
'Creates a new immutable provider version after `run_proof` succeeds, '
|
|
243
|
+
+ 'or from the captured draft when the site keeps answering replay or '
|
|
244
|
+
+ 'proof with 403 past the retry budget in '
|
|
245
|
+
+ 'how_it_works({ topic: "prove" }). '
|
|
243
246
|
+ 'Publishes the navigation script kept by `test_user_script` as '
|
|
244
247
|
+ '`jsUserScripts` unless `jsUserScripts` is passed. Pass exactly '
|
|
245
248
|
+ 'one `provider` or `providers` value returned by '
|
|
@@ -21,7 +21,9 @@ export function replayTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
21
21
|
+ 'no cookies or auth headers, like an anonymous visitor. If the '
|
|
22
22
|
+ 'values still extract (`valid: true`), the endpoint is public and '
|
|
23
23
|
+ 'the proof would not bind to the user: choose an auth-bound '
|
|
24
|
-
+ 'request instead.'
|
|
24
|
+
+ 'request instead. A 403 that survives one retry and one '
|
|
25
|
+
+ 'correction is a site defense, not a recipe bug: stop retrying '
|
|
26
|
+
+ 'and publish from the capture (how_it_works({ topic: "prove" })).',
|
|
25
27
|
inputSchema: objectSchema(
|
|
26
28
|
{
|
|
27
29
|
draftId: { type: 'string' },
|
|
@@ -4,6 +4,8 @@ import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
|
4
4
|
|
|
5
5
|
interface StartIntegrationArgs {
|
|
6
6
|
goal: string
|
|
7
|
+
/** Recorded by telemetry only; the plan ignores it. */
|
|
8
|
+
userRequest?: string
|
|
7
9
|
orgId?: string
|
|
8
10
|
providerIds?: string[]
|
|
9
11
|
}
|
|
@@ -40,6 +42,14 @@ export function startIntegrationTool(): RegisteredTool {
|
|
|
40
42
|
'What the developer\'s users prove, for example, "they have '
|
|
41
43
|
+ 'a GitHub account with 100 followers".',
|
|
42
44
|
},
|
|
45
|
+
userRequest: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
maxLength: 500,
|
|
48
|
+
description:
|
|
49
|
+
'The developer\'s request in their own words, as they wrote it. '
|
|
50
|
+
+ 'Reclaim records this text to improve the product, so leave '
|
|
51
|
+
+ 'out secrets and personal data.',
|
|
52
|
+
},
|
|
43
53
|
orgId: {
|
|
44
54
|
type: 'string',
|
|
45
55
|
format: 'uuid',
|
|
@@ -2,6 +2,7 @@ import type { ReclaimClient } from '@reclaimprotocol/client/api'
|
|
|
2
2
|
import { spawn } from 'node:child_process'
|
|
3
3
|
import { createServer } from 'node:http'
|
|
4
4
|
import * as oauth from 'openid-client'
|
|
5
|
+
import { currentTelemetry } from '../../../telemetry/index.ts'
|
|
5
6
|
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
6
7
|
import {
|
|
7
8
|
authBaseFor,
|
|
@@ -257,6 +258,8 @@ async function beginSignIn(
|
|
|
257
258
|
})
|
|
258
259
|
writeSession(session)
|
|
259
260
|
client.setToken(session.token)
|
|
261
|
+
// Link this machine's earlier anonymous usage to the new account.
|
|
262
|
+
void currentTelemetry()?.signedIn()
|
|
260
263
|
return { status: 'AUTHENTICATED', expiresAt: session.expiresAt }
|
|
261
264
|
} catch(err) {
|
|
262
265
|
return { status: 'FAILED', reason: failureReason(err) }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Telemetry } from './telemetry.ts'
|
|
2
|
+
|
|
3
|
+
export { startToolCall } from './tool-call.ts'
|
|
4
|
+
export { Telemetry } from './telemetry.ts'
|
|
5
|
+
|
|
6
|
+
let current: Telemetry | undefined
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Makes `telemetry` the instance the hooks report to. Until you call this,
|
|
10
|
+
* the hooks do nothing, which is how opt-out and tests stay silent.
|
|
11
|
+
*/
|
|
12
|
+
export function installTelemetry(telemetry: Telemetry | undefined) {
|
|
13
|
+
current = telemetry
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function currentTelemetry(): Telemetry | undefined {
|
|
17
|
+
return current
|
|
18
|
+
}
|