@reclaimprotocol/agent 0.1.4 → 0.1.5

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.
@@ -92,7 +92,7 @@ export const MCP_TOOLS = {
92
92
  }),
93
93
  },
94
94
  "AdminProviders": {
95
- description: "All verifications (admin-only)\n\nAdmin only. Lists all verifications across all organizations. Supports filtering by visibility, status, and free-text search on title/domain/slug.",
95
+ description: "All providers (admin-only)\n\nAdmin only. Lists all providers across all organizations. Supports filtering by visibility, status, and free-text search on title/domain/slug.",
96
96
  inputSchema: toZod({
97
97
  "type": "object",
98
98
  "properties": {
@@ -455,7 +455,7 @@ export const MCP_TOOLS = {
455
455
  }),
456
456
  },
457
457
  "CreateProvider": {
458
- description: "Create a verification\n\nCreate a new Reclaim verification. Use this only after `providers` returned nothing useful for the domain. The caller must be an active member of the target organization.\nCreates a new verification in the specified organization. `visibility` is coerced to `PRIVATE` at creation regardless of what's sent; the author may toggle to `PUBLIC` later via `PATCH /providers/{id}`. While `PRIVATE`, versions go to the go live freely; once `PUBLIC`, non-admin versions need admin review. See `Provider.visibility`.\nThe server also seeds an initial `0.1.0` version on the `draft` branch in the same transaction so the verification is immediately editable. Its `initialUrl` defaults to `https://<domain>`; all other version fields fall back to their schema-declared defaults (structured verification, empty requests, empty web settings). `Provider.latestVersion` stays `null` until the draft is promoted to the live channel.",
458
+ description: "Create a provider\n\nCreate a new Reclaim provider. Use this only after `providers` returned nothing useful for the domain. The caller must be an active member of the target organization.\nCreates a new provider in the specified organization. `visibility` is coerced to `PRIVATE` at creation regardless of what's sent; the author may toggle to `PUBLIC` later via `PATCH /providers/{id}`. While `PRIVATE`, versions go to the go live freely; once `PUBLIC`, non-admin versions need admin review. See `Provider.visibility`.\nThe server also seeds an initial `0.1.0` version on the `draft` branch in the same transaction so the provider is immediately editable. Its `initialUrl` defaults to `https://<domain>`; all other version fields fall back to their schema-declared defaults (a `structured` verificationType, empty requests, empty web settings). `Provider.latestVersion` stays `null` until the draft is promoted to the live channel.",
459
459
  inputSchema: toZod({
460
460
  "type": "object",
461
461
  "properties": {
@@ -582,7 +582,7 @@ export const MCP_TOOLS = {
582
582
  }),
583
583
  },
584
584
  "CreateProviderVersion": {
585
- description: "Create a new verification version\n\nCreates a new version of the verification. The starting branch\ndepends on the verification's `visibility` and the caller's role:\n\n- `PRIVATE` verification (any caller): the live channel (no review).\n\n- `PUBLIC` verification, admin caller (Reclaim staff): the live channel\n (no review).\n\n- `PUBLIC` verification, non-admin caller: `draft` (or `ai` if\n authored by the AI assistant). Must pass review (see\n `POST /providers/{id}/versions/{version}/review`) before\n reaching the live channel.\n",
585
+ description: "Create a new provider version\n\nCreates a new version of the provider. The starting branch\ndepends on the provider's `visibility` and the caller's role:\n\n- `PRIVATE` provider (any caller): the live channel (no review).\n\n- `PUBLIC` provider, admin caller (Reclaim staff): the live channel\n (no review).\n\n- `PUBLIC` provider, non-admin caller: `draft` (or `ai` if\n authored by the AI assistant). Must pass review (see\n `POST /providers/{id}/versions/{version}/review`) before\n reaching the live channel.\n",
586
586
  inputSchema: toZod({
587
587
  "type": "object",
588
588
  "properties": {
@@ -606,7 +606,7 @@ export const MCP_TOOLS = {
606
606
  "body": {
607
607
  "type": "object",
608
608
  "additionalProperties": false,
609
- "description": "Body for creating a new version of a provider. Every new version starts on the `draft` branch — see `CreateProviderVersion`. The request body never sets `branch` directly; going live is a separate, explicit step (publish for PRIVATE, submit-for-review → approve for PUBLIC). The version's `verificationType` is always `structured` and is not settable here; a `prompt` field is reserved for a future AI authoring flow and is also not accepted on this surface.",
609
+ "description": "Body for creating a new version of a provider. Every new version starts on the `draft` branch — see `CreateProviderVersion`. The request body never sets `branch` directly; going live is a separate, explicit step (publish for PRIVATE, submit-for-review → approve for PUBLIC). `verificationType` defaults to `structured`; pass `llm` with a `prompt` to author an AI-verification provider.",
610
610
  "required": [
611
611
  "initialUrl",
612
612
  "requests",
@@ -614,6 +614,20 @@ export const MCP_TOOLS = {
614
614
  "webSettings"
615
615
  ],
616
616
  "properties": {
617
+ "verificationType": {
618
+ "type": "string",
619
+ "description": "How a provider version verifies a claim. `structured` uses the explicit match rules on this version (the `matches` / `allowedJsMatches` and related settings). `llm` defers the verification decision to a language model and requires `prompt` to be set.",
620
+ "enum": [
621
+ "llm",
622
+ "structured"
623
+ ]
624
+ },
625
+ "prompt": {
626
+ "type": "string",
627
+ "description": "Extraction prompt, used (and required) when `verificationType` is `llm`.",
628
+ "minLength": 0,
629
+ "maxLength": 1000000
630
+ },
617
631
  "version": {
618
632
  "type": "object",
619
633
  "additionalProperties": false,
@@ -1165,7 +1179,7 @@ export const MCP_TOOLS = {
1165
1179
  }),
1166
1180
  },
1167
1181
  "DecideProviderVersionReview": {
1168
- description: "Record a decision on a verification version review\n\nAdmin only (Reclaim staff). Approves or rejects a verification version currently on the `in_review` branch. `APPROVED` clears the branch tag and promotes the version to the live channel; `REJECTED` moves it to the `rejected` branch where the author can address feedback and resubmit. A rejected version stays rejected — the author either fixes and resubmits, or abandons it.\nUntil RBAC is designed, \"admin\" is detected via the user's email domain (`@reclaimprotocol.org`). Non-admin callers receive a 403.",
1182
+ description: "Record a decision on a provider version review\n\nAdmin only (Reclaim staff). Approves or rejects a provider version currently on the `in_review` branch. `APPROVED` clears the branch tag and promotes the version to the live channel; `REJECTED` moves it to the `rejected` branch where the author can address feedback and resubmit. A rejected version stays rejected — the author either fixes and resubmits, or abandons it.\nUntil RBAC is designed, \"admin\" is detected via the user's email domain (`@reclaimprotocol.org`). Non-admin callers receive a 403.",
1169
1183
  inputSchema: toZod({
1170
1184
  "type": "object",
1171
1185
  "properties": {
@@ -1330,7 +1344,7 @@ export const MCP_TOOLS = {
1330
1344
  }),
1331
1345
  },
1332
1346
  "DeleteProvider": {
1333
- description: "Delete a verification\n\nOwner of the verification's organization only. Soft-archives the verification (sets `status` to `ARCHIVED`) so historical references remain resolvable. Supports optimistic concurrency via `If-Match`.",
1347
+ description: "Delete a provider\n\nOwner of the provider's organization only. Soft-archives the provider (sets `status` to `ARCHIVED`) so historical references remain resolvable. Supports optimistic concurrency via `If-Match`.",
1334
1348
  inputSchema: toZod({
1335
1349
  "type": "object",
1336
1350
  "properties": {
@@ -1373,7 +1387,7 @@ export const MCP_TOOLS = {
1373
1387
  }),
1374
1388
  },
1375
1389
  "DeleteProviderVersion": {
1376
- description: "Delete a verification version\n\nSoft-archives a version on the `draft`, `ai`, or `rejected` branch — sets `status` to `ARCHIVED` so history is preserved. Trunk (live) and `in_review` versions cannot be archived this way; archive the parent verification instead. Supports optimistic concurrency via `If-Match`.",
1390
+ description: "Delete a provider version\n\nSoft-archives a version on the `draft`, `ai`, or `rejected` branch — sets `status` to `ARCHIVED` so history is preserved. Trunk (live) and `in_review` versions cannot be archived this way; archive the parent provider instead. Supports optimistic concurrency via `If-Match`.",
1377
1391
  inputSchema: toZod({
1378
1392
  "type": "object",
1379
1393
  "properties": {
@@ -1461,7 +1475,7 @@ export const MCP_TOOLS = {
1461
1475
  }),
1462
1476
  },
1463
1477
  "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.",
1478
+ description: "Resolve a website + data points to 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
1479
  inputSchema: toZod({
1466
1480
  "type": "object",
1467
1481
  "properties": {
@@ -1718,7 +1732,7 @@ export const MCP_TOOLS = {
1718
1732
  }),
1719
1733
  },
1720
1734
  "GetProvider": {
1721
- description: "Get a verification\n\nFetch a single Reclaim verification by id. Returns every field needed\nto integrate it into a flow: domain, the live version (initialUrl + requests),\nand the data points it can prove. Use `providers` first if you only\nhave a query string.\n\nReturns the verification record.\n\n**Canonical URL.** For SEO and stable sharing, the canonical\nHTML URL for a verification is `/providers/{providerId}/{slug}`\n(see `GetProviderBySlug`). HTML callers (`Accept: text/html`)\nthat hit this route — which omits the slug — receive a `301`\nredirect to the slugged URL. JSON callers\n(`Accept: application/json`) receive the body directly without\nredirect; the slug is on the response.\n",
1735
+ description: "Get a provider\n\nFetch a single Reclaim provider by id. Returns every field needed\nto integrate it into a flow: domain, the live version (initialUrl + requests),\nand the data points it can prove. Use `providers` first if you only\nhave a query string.\n\nReturns the provider record.\n\n**Canonical URL.** For SEO and stable sharing, the canonical\nHTML URL for a provider is `/providers/{providerId}/{slug}`\n(see `GetProviderBySlug`). HTML callers (`Accept: text/html`)\nthat hit this route — which omits the slug — receive a `301`\nredirect to the slugged URL. JSON callers\n(`Accept: application/json`) receive the body directly without\nredirect; the slug is on the response.\n",
1722
1736
  inputSchema: toZod({
1723
1737
  "type": "object",
1724
1738
  "properties": {
@@ -1751,7 +1765,7 @@ export const MCP_TOOLS = {
1751
1765
  }),
1752
1766
  },
1753
1767
  "GetProviderVersion": {
1754
- description: "Get a specific verification version",
1768
+ description: "Get a specific provider version",
1755
1769
  inputSchema: toZod({
1756
1770
  "type": "object",
1757
1771
  "properties": {
@@ -2069,7 +2083,7 @@ export const MCP_TOOLS = {
2069
2083
  }),
2070
2084
  },
2071
2085
  "ListProviderVersions": {
2072
- description: "List a verification's versions\n\nLists versions visible to the caller. Non-public branches (`ai`, `draft`, `in_review`, `rejected`) are only visible to members of the verification's owning organization.",
2086
+ description: "List a provider's versions\n\nLists versions visible to the caller. Non-public branches (`ai`, `draft`, `in_review`, `rejected`) are only visible to members of the provider's owning organization.",
2073
2087
  inputSchema: toZod({
2074
2088
  "type": "object",
2075
2089
  "properties": {
@@ -2218,7 +2232,7 @@ export const MCP_TOOLS = {
2218
2232
  }),
2219
2233
  },
2220
2234
  "Providers": {
2221
- description: "Search verifications\n\nSearch published Reclaim verifications by domain or data point. A verification is the recipe an end-user follows to prove something about themselves (e.g. \"I have N followers on Twitter\"). Returns a list of matches. Call this BEFORE `create_provider` — most popular domains already have a public verification you can reuse.\nCatalog page listing all verifications. Returns a paginated list of verifications the caller can see. Public verifications are visible to everyone; private verifications are only visible to org members.",
2235
+ description: "Search providers\n\nSearch published Reclaim providers by domain or data point. A provider is the recipe an end-user follows to prove something about themselves (e.g. \"I have N followers on Twitter\"). Returns a list of matches. Call this BEFORE `create_provider` — most popular domains already have a public provider you can reuse.\nCatalog page listing all providers. Returns a paginated list of providers the caller can see. Public providers are visible to everyone; private providers are only visible to org members.",
2222
2236
  inputSchema: toZod({
2223
2237
  "type": "object",
2224
2238
  "properties": {
@@ -2262,7 +2276,7 @@ export const MCP_TOOLS = {
2262
2276
  }),
2263
2277
  },
2264
2278
  "PublishProviderVersion": {
2265
- description: "Publish a draft version directly (PRIVATE providers only)\n\nPublishes a verification version on the `draft` branch directly to the live channel (trunk). Only allowed for PRIVATE providers — PUBLIC providers must use the review flow (`POST .../review` → admin decision). On success the version's branch transitions from `draft` to `null` (trunk/live).",
2279
+ description: "Publish a draft version directly (PRIVATE providers only)\n\nPublishes a provider version on the `draft` branch directly to the live channel (trunk). Only allowed for PRIVATE providers — PUBLIC providers must use the review flow (`POST .../review` → admin decision). On success the version's branch transitions from `draft` to `null` (trunk/live).",
2266
2280
  inputSchema: toZod({
2267
2281
  "type": "object",
2268
2282
  "properties": {
@@ -2503,7 +2517,7 @@ export const MCP_TOOLS = {
2503
2517
  }),
2504
2518
  },
2505
2519
  "SubmitProviderVersionForReview": {
2506
- description: "Submit a verification version for review\n\nSubmits a verification version (currently on the `draft` or `rejected` branch) for admin review. On success the version's branch transitions to `in_review`. Callable by any authenticated member of the verification's owning org. Meaningful only on `PUBLIC` verifications — on `PRIVATE` verifications new versions go to the live channel directly and never need review.",
2520
+ description: "Submit a provider version for review\n\nSubmits a provider version (currently on the `draft` or `rejected` branch) for admin review. On success the version's branch transitions to `in_review`. Callable by any authenticated member of the provider's owning org. Meaningful only on `PUBLIC` providers — on `PRIVATE` providers new versions go to the live channel directly and never need review.",
2507
2521
  inputSchema: toZod({
2508
2522
  "type": "object",
2509
2523
  "properties": {
@@ -2552,7 +2566,7 @@ export const MCP_TOOLS = {
2552
2566
  }),
2553
2567
  },
2554
2568
  "TransferProvider": {
2555
- description: "Transfer a verification to a different workspace\n\nAdmin-only. Moves the verification's `orgId` to the supplied workspace; preserves slug, versions, members. SQL function `builder.transfer_provider` enforces the admin gate and target existence atomically.",
2569
+ description: "Transfer a provider to a different workspace\n\nAdmin-only. Moves the provider's `orgId` to the supplied workspace; preserves slug, versions, members. SQL function `builder.transfer_provider` enforces the admin gate and target existence atomically.",
2556
2570
  inputSchema: toZod({
2557
2571
  "type": "object",
2558
2572
  "properties": {
@@ -2705,7 +2719,7 @@ export const MCP_TOOLS = {
2705
2719
  }),
2706
2720
  },
2707
2721
  "UpdateProvider": {
2708
- description: "Update a verification\n\nUpdate a Reclaim verification's metadata. Partial PATCH — pass only fields you want to change. The caller must be an active member of the owning organization. To change the verification's recipe (initialUrl, requests, …) cut a new version via the dashboard. Use `seo` to control how the verification appears in search results and social shares; omit a subfield to keep the server default.\nCaller must be a member of the verification's owning organization. `id`, `orgId`, and `slug` are immutable. Supports optimistic concurrency via `If-Match`.",
2722
+ description: "Update a provider\n\nUpdate a Reclaim provider's metadata. Partial PATCH — pass only fields you want to change. The caller must be an active member of the owning organization. To change the provider's recipe (initialUrl, requests, …) cut a new version via the dashboard. Use `seo` to control how the provider appears in search results and social shares; omit a subfield to keep the server default.\nCaller must be a member of the provider's owning organization. `id`, `orgId`, and `slug` are immutable. Supports optimistic concurrency via `If-Match`.",
2709
2723
  inputSchema: toZod({
2710
2724
  "type": "object",
2711
2725
  "properties": {
@@ -2840,7 +2854,7 @@ export const MCP_TOOLS = {
2840
2854
  }),
2841
2855
  },
2842
2856
  "UpdateProviderVersion": {
2843
- description: "Update a verification version\n\nMutable only while the version is on the `draft` or `rejected` branch. Versions on `in_review` or the live channel reject all writes here. Caller must be a member of the verification's owning organization. Supports optimistic concurrency via `If-Match`.",
2857
+ description: "Update a provider version\n\nMutable only while the version is on the `draft` or `rejected` branch. Versions on `in_review` or the live channel reject all writes here. Caller must be a member of the provider's owning organization. Supports optimistic concurrency via `If-Match`.",
2844
2858
  inputSchema: toZod({
2845
2859
  "type": "object",
2846
2860
  "properties": {
@@ -2880,13 +2894,27 @@ export const MCP_TOOLS = {
2880
2894
  "body": {
2881
2895
  "type": "object",
2882
2896
  "additionalProperties": false,
2883
- "description": "Partial update of a provider version. Mutable only while the version is on the `draft` or `rejected` branch; versions on `in_review` or the live channel reject all writes here. The semver does not change on update — `build_number` increments instead, giving callers a monotonic revision counter without touching the (major, minor, patch) PK. `verificationType` / `prompt` are not accepted; see `ProviderVersion` for why.",
2897
+ "description": "Partial update of a provider version. Mutable only while the version is on the `draft` or `rejected` branch; versions on `in_review` or the live channel reject all writes here. The semver does not change on update — `build_number` increments instead, giving callers a monotonic revision counter without touching the (major, minor, patch) PK. `verificationType` and `prompt` may be updated; omit either to carry the prior build's value forward.",
2884
2898
  "properties": {
2885
2899
  "initialUrl": {
2886
2900
  "type": "string",
2887
2901
  "format": "uri",
2888
2902
  "maxLength": 300
2889
2903
  },
2904
+ "verificationType": {
2905
+ "type": "string",
2906
+ "description": "How a provider version verifies a claim. `structured` uses the explicit match rules on this version (the `matches` / `allowedJsMatches` and related settings). `llm` defers the verification decision to a language model and requires `prompt` to be set.",
2907
+ "enum": [
2908
+ "llm",
2909
+ "structured"
2910
+ ]
2911
+ },
2912
+ "prompt": {
2913
+ "type": "string",
2914
+ "description": "Extraction prompt, used when `verificationType` is `llm`.",
2915
+ "minLength": 0,
2916
+ "maxLength": 1000000
2917
+ },
2890
2918
  "requests": {
2891
2919
  "type": "array",
2892
2920
  "minItems": 0,
@@ -5,16 +5,32 @@ export interface ToolDefinition {
5
5
  description: string;
6
6
  inputSchema: JSONSchema.ObjectSchema;
7
7
  }
8
+ /**
9
+ * Passed to every tool handler alongside its parsed args. Long-running,
10
+ * human-in-the-loop tools (e.g. a browser login flow that waits for someone
11
+ * to sign in) should call `reportProgress` every few seconds while waiting.
12
+ * MCP clients that attach a progress token to their request (most desktop
13
+ * clients do) treat an incoming `notifications/progress` as proof the call
14
+ * is still alive and reset their own request timeout instead of giving up —
15
+ * the SDK's client-side default is 60s, far shorter than a human can be
16
+ * expected to take to log in. A no-op when the client didn't request
17
+ * progress tracking for this call.
18
+ */
19
+ export interface ToolContext {
20
+ reportProgress: (message: string) => Promise<void>;
21
+ }
8
22
  export interface RegisteredTool {
9
23
  definition: ToolDefinition;
10
24
  /**
11
25
  * @param args Tool arguments
26
+ * @param ctx Progress-reporting context (see {@link ToolContext}).
27
+ * Optional so direct/test callers can omit it.
12
28
  * @returns A JSON stringifiable content to be returned to the llm
13
29
  * @throws Any error encountered during tool execution will be caught and
14
30
  * returned as a response using
15
31
  * [remapErrorAsResponse](./src/mcp/remap_errors.ts).
16
32
  */
17
- handler: (args: unknown) => Promise<unknown>;
33
+ handler: (args: unknown, ctx?: ToolContext) => Promise<unknown>;
18
34
  }
19
35
  /**
20
36
  * Define a tool with a single inputSchema (JSON Schema) used as the
@@ -23,7 +39,7 @@ export interface RegisteredTool {
23
39
  * so the description sent to the MCP client and the validation applied
24
40
  * to inbound args cannot drift.
25
41
  */
26
- export declare function defineTool<TArgs>(definition: ToolDefinition, handler: (args: TArgs) => Promise<unknown>): RegisteredTool;
42
+ export declare function defineTool<TArgs>(definition: ToolDefinition, handler: (args: TArgs, ctx: ToolContext) => Promise<unknown>): RegisteredTool;
27
43
  /**
28
44
  * Registers a list of RegisteredTool entries on a high-level McpServer.
29
45
  * Converts JSON Schema definitions to Zod and provides consistent
package/lib/mcp/server.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { toZod } from "./api/to-zod.js";
2
2
  import { remapErrorAsResponse } from "./remap_errors.js";
3
+ const NOOP_CONTEXT = { reportProgress: async () => { } };
3
4
  /**
4
5
  * Define a tool with a single inputSchema (JSON Schema) used as the
5
6
  * source of truth. The handler receives args already parsed and
@@ -11,9 +12,9 @@ export function defineTool(definition, handler) {
11
12
  const schema = toZod(definition.inputSchema);
12
13
  return {
13
14
  definition,
14
- handler: async (raw) => {
15
+ handler: async (raw, ctx = NOOP_CONTEXT) => {
15
16
  const args = schema.parse(raw);
16
- return await handler(args);
17
+ return await handler(args, ctx);
17
18
  },
18
19
  };
19
20
  }
@@ -27,13 +28,27 @@ export function registerToolList(server, tools) {
27
28
  server.registerTool(tool.definition.name, {
28
29
  description: tool.definition.description,
29
30
  inputSchema: toZod(tool.definition.inputSchema),
30
- }, async (args) => {
31
+ }, async (args, extra) => {
31
32
  try {
33
+ const progressToken = extra?._meta?.progressToken;
34
+ let progress = 0;
35
+ const ctx = {
36
+ reportProgress: async (message) => {
37
+ if (progressToken === undefined) {
38
+ return;
39
+ }
40
+ progress += 1;
41
+ await extra.sendNotification({
42
+ method: 'notifications/progress',
43
+ params: { progressToken, progress, message },
44
+ }).catch(() => { });
45
+ },
46
+ };
32
47
  return {
33
48
  content: [
34
49
  {
35
50
  type: 'text',
36
- text: JSON.stringify(await tool.handler(args), null, 2),
51
+ text: JSON.stringify(await tool.handler(args, ctx), null, 2),
37
52
  },
38
53
  ],
39
54
  };
@@ -89,7 +89,7 @@ const ALLOWED_JS_REQUEST_SCHEMA = {
89
89
  export function createVersionFromCaptureTool(client) {
90
90
  return defineTool({
91
91
  name: 'create_provider_version_from_capture',
92
- description: 'Persist drafted ReclaimProvider(s) as a verification version. ' +
92
+ description: 'Persist drafted ReclaimProvider(s) as a provider version. ' +
93
93
  'Call this after run_proof succeeds, passing the `provider` ' +
94
94
  'object(s) from propose_provider VERBATIM. This tool builds the ' +
95
95
  'body (the requests[] array, webSettings, etc.) for you — do NOT ' +
@@ -120,7 +120,7 @@ export function createVersionFromCaptureTool(client) {
120
120
  properties: {
121
121
  providerId: {
122
122
  type: 'string',
123
- description: 'UUID of the verification to publish the version on.',
123
+ description: 'UUID of the provider to publish the version on.',
124
124
  },
125
125
  provider: PROVIDER_SCHEMA,
126
126
  providers: {
@@ -0,0 +1,6 @@
1
+ import type { RegisteredTool } from '../../server.ts';
2
+ import type { CaptureStore } from './capture.ts';
3
+ /** The shared authoring tools as MCP `RegisteredTool`s: each resolves its
4
+ * capture's local backend from `args.captureId`; the schema carries
5
+ * `captureId` plus the rich `run_proof` contract. */
6
+ export declare function sharedAuthoringTools(captures: CaptureStore): RegisteredTool[];
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Build the shared authoring tools for the MCP, bound to a per-capture LOCAL
3
+ * backend: a Chrome tab over CDP + the in-process attestor SDK. The tool logic
4
+ * (navigate/inspect/propose/replay/prove) is authored once in `../../../
5
+ * authoring` with the shared `defineTool` primitive; this only supplies the
6
+ * MCP host — resolve the backend from `captureId` (the session handle from
7
+ * `start_capture`, since one capture bundles the tab, its requests, and its
8
+ * drafts) and the rich, credential-aware `run_proof` schema.
9
+ */
10
+ import { buildAuthoringTools, } from "../../../authoring/index.js";
11
+ import { runAttestorProof } from "../../../proof/attestor.js";
12
+ import { replayProvider } from "../../../proof/replay.js";
13
+ import { shapeRichProofResult } from "./proof.js";
14
+ /** The local backend for one capture: drive its tab over CDP, read its
15
+ * captured requests, prove via the in-process attestor, replay by re-fetch. */
16
+ function localBackend(entry) {
17
+ const { tabClient, session, drafts } = entry;
18
+ // The per-tab CDP client is strongly typed to `keyof Commands`; the shared
19
+ // tools drive it with dynamic method/event names, so view it as string-keyed.
20
+ const client = tabClient;
21
+ return {
22
+ capture: session,
23
+ drafts,
24
+ sendCdp: (method, params) => client.send(method, params ?? {}),
25
+ waitForEvent: (method, timeoutMs) => new Promise((resolve) => {
26
+ const onEvent = () => {
27
+ cleanup();
28
+ resolve('fired');
29
+ };
30
+ const timer = setTimeout(() => {
31
+ cleanup();
32
+ resolve('timeout');
33
+ }, timeoutMs);
34
+ const cleanup = () => {
35
+ clearTimeout(timer);
36
+ client.removeListener(method, onEvent);
37
+ };
38
+ client.once(method, onEvent);
39
+ }),
40
+ replay: async (draft, opts) => {
41
+ // Auth-bound check: re-fetch with the secrets dropped. replayProvider
42
+ // already does a live Node fetch, so empty secrets == anonymous.
43
+ const secrets = opts?.withoutSecrets ? {} : draft.secrets;
44
+ const result = await replayProvider(draft.provider, secrets);
45
+ return {
46
+ status: result.status,
47
+ matchesOriginal: result.matchesOriginal,
48
+ bodySnippet: result.body.slice(0, 256),
49
+ ...(result.hints !== undefined ? { hints: result.hints } : {}),
50
+ };
51
+ },
52
+ prove: async (draft, opts) => {
53
+ const result = await runAttestorProof(draft.provider, draft.secrets, {
54
+ ...(opts?.ownerAddress ? { ownerAddress: opts.ownerAddress } : {}),
55
+ ...(opts?.attestorUrl ? { attestorUrl: opts.attestorUrl } : {}),
56
+ });
57
+ return shapeRichProofResult(result, {
58
+ provider: draft.provider,
59
+ hasSecrets: Object.keys(draft.secrets).length > 0,
60
+ ...(opts?.attestorUrl ? { attestorUrl: opts.attestorUrl } : {}),
61
+ });
62
+ },
63
+ };
64
+ }
65
+ /** The MCP's rich, credential-aware `run_proof` schema, merged onto the shared
66
+ * tool by the host below (the cloud's TEE signs in-enclave, so it needs none
67
+ * of this). */
68
+ const RUN_PROOF_EXTRAS = {
69
+ description: 'Run a full zkTLS proof of the drafted recipe through the Reclaim '
70
+ + 'attestor SDK. Use only after replay_request looks right — attestor '
71
+ + 'calls are expensive. Writes the full claim to '
72
+ + '~/.reclaim/claims/<timestamp>.json and returns identifier + '
73
+ + 'extractedParameters + claimPath + a prettified `claim` (request '
74
+ + 'dropped, byte arrays as base64). With RECLAIM_AGENT_TRACE=1, also '
75
+ + 'writes a per-call diagnostic dump (input provider, sentParams, full '
76
+ + 'attestor log stream, result) and returns it as `runLogPath`. '
77
+ + '`attestorUrl` overrides the default only when the dev supplies a '
78
+ + 'specific alternative — do NOT invent backup URLs. `ownerAddress` '
79
+ + '(the credential to sign with) is REQUIRED — call resolve_owner_key '
80
+ + 'first. errorKind="missing-owner-key" means that address has no known '
81
+ + 'private key; re-run resolve_owner_key or issue/import a credential.',
82
+ properties: {
83
+ ownerAddress: {
84
+ type: 'string',
85
+ pattern: '^0x[0-9a-fA-F]{40}$',
86
+ description: 'ETH address of the credential to sign the claim with. Get it '
87
+ + 'from resolve_owner_key first. The private key is resolved '
88
+ + 'locally (cache / env / .env) — never passed here.',
89
+ },
90
+ attestorUrl: { type: 'string' },
91
+ },
92
+ required: ['ownerAddress'],
93
+ };
94
+ /** The shared authoring tools as MCP `RegisteredTool`s: each resolves its
95
+ * capture's local backend from `args.captureId`; the schema carries
96
+ * `captureId` plus the rich `run_proof` contract. */
97
+ export function sharedAuthoringTools(captures) {
98
+ const host = {
99
+ resolveBackend: (args) => {
100
+ const captureId = String(args.captureId);
101
+ const entry = captures.sessions.get(captureId);
102
+ if (!entry) {
103
+ throw new Error(`no capture ${captureId} — call start_capture first `
104
+ + '(it has been reset or never started).');
105
+ }
106
+ return localBackend(entry);
107
+ },
108
+ sessionSchema: {
109
+ properties: { captureId: { type: 'string' } },
110
+ required: ['captureId'],
111
+ },
112
+ toolExtras: { run_proof: RUN_PROOF_EXTRAS },
113
+ };
114
+ return buildAuthoringTools(host);
115
+ }
@@ -17,4 +17,9 @@ export declare function captureFirebaseTokenViaCdp(opts: {
17
17
  loginUrl?: string;
18
18
  timeoutMs?: number;
19
19
  pollMs?: number;
20
+ /** Called every ~10s while waiting for the human to finish signing in, so
21
+ * MCP clients that track progress on this call can reset their own
22
+ * request timeout instead of giving up on a login that's still in
23
+ * flight (see `ToolContext` in `mcp/server.ts`). */
24
+ onProgress?: (message: string) => Promise<void>;
20
25
  }): Promise<CdpLoginResult>;
@@ -65,6 +65,8 @@ export async function captureFirebaseTokenViaCdp(opts) {
65
65
  const loginUrl = opts.loginUrl || oldLoginUrl() || DEFAULT_LOGIN_URL;
66
66
  const timeoutMs = opts.timeoutMs ?? 180_000;
67
67
  const pollMs = opts.pollMs ?? 1000;
68
+ const onProgress = opts.onProgress ?? (async () => { });
69
+ const progressIntervalMs = 10_000;
68
70
  const cfg = new ConfigStore().read()?.browser_agent;
69
71
  const mode = cfg?.attachMode ?? 'dedicated';
70
72
  const port = cfg?.port ?? 9222;
@@ -89,7 +91,9 @@ export async function captureFirebaseTokenViaCdp(opts) {
89
91
  try {
90
92
  const session = await handles.attachToTab(targetId);
91
93
  const { Runtime } = session;
92
- const deadline = Date.now() + timeoutMs;
94
+ const startedAt = Date.now();
95
+ const deadline = startedAt + timeoutMs;
96
+ let lastProgressAt = startedAt;
93
97
  while (Date.now() < deadline) {
94
98
  const r = await Runtime.evaluate({
95
99
  expression: EXTRACT_TOKEN_JS,
@@ -100,6 +104,13 @@ export async function captureFirebaseTokenViaCdp(opts) {
100
104
  if (typeof value === 'string' && value.length > 0) {
101
105
  return { token: value, closedBrowser: launchedByUs };
102
106
  }
107
+ const now = Date.now();
108
+ if (now - lastProgressAt >= progressIntervalMs) {
109
+ lastProgressAt = now;
110
+ const remainingSec = Math.round((deadline - now) / 1000);
111
+ await onProgress(`Still waiting for sign-in at ${loginUrl} ` +
112
+ `(${remainingSec}s left before this call times out).`);
113
+ }
103
114
  await new Promise((resolve) => setTimeout(resolve, pollMs));
104
115
  }
105
116
  throw new Error('Timed out waiting for login. Finish signing in at the opened ' +
@@ -1,5 +1,5 @@
1
1
  import { agentDisabled } from "../../consts.js";
2
- import { defineTool } from "../../mcp/server.js";
2
+ import { defineTool, } from "../../mcp/server.js";
3
3
  import { persistIdentity, toEthUid } from "../identity.js";
4
4
  import { captureFirebaseTokenViaCdp } from "../login-cdp.js";
5
5
  /**
@@ -69,7 +69,7 @@ export function authenticateTool(client) {
69
69
  },
70
70
  },
71
71
  },
72
- }, async ({ token, ethAddress, loginUrl, timeoutMs, useBrowser }) => {
72
+ }, async ({ token, ethAddress, loginUrl, timeoutMs, useBrowser }, ctx) => {
73
73
  if (token && ethAddress) {
74
74
  throw new Error('Provide only ONE of `token` or `ethAddress`, not both.');
75
75
  }
@@ -113,7 +113,11 @@ export function authenticateTool(client) {
113
113
  throw new Error('Browser login is unavailable (RECLAIM_AGENT_DISABLED=1). Pass a ' +
114
114
  '`token` (Firebase ID token) or `ethAddress` instead.');
115
115
  }
116
- const result = await captureFirebaseTokenViaCdp({ loginUrl, timeoutMs });
116
+ const result = await captureFirebaseTokenViaCdp({
117
+ loginUrl,
118
+ timeoutMs,
119
+ onProgress: ctx.reportProgress,
120
+ });
117
121
  const identity = { kind: 'bearer', token: result.token };
118
122
  await validate(identity);
119
123
  persistIdentity(identity);
@@ -18,7 +18,7 @@ export declare function ensureAttestorCrypto(): void;
18
18
  * default. Resolved per-call so an env change or per-request override takes
19
19
  * effect without restarting. */
20
20
  export declare function resolveAttestorUrl(override?: string): string;
21
- /** Map a verification's provider recipe straight to the attestor's HTTP
21
+ /** Map a provider's recipe straight to the attestor's HTTP
22
22
  * params — never synthesized from captured traffic. */
23
23
  export declare function buildHttpParams(provider: ReclaimProvider): ProviderParams<'http'>;
24
24
  export interface ParsedClaim {
@@ -21,7 +21,7 @@ export function ensureAttestorCrypto() {
21
21
  export function resolveAttestorUrl(override) {
22
22
  return (override ?? attestorUrlOverride() ?? FALLBACK_ATTESTOR_URL);
23
23
  }
24
- /** Map a verification's provider recipe straight to the attestor's HTTP
24
+ /** Map a provider's recipe straight to the attestor's HTTP
25
25
  * params — never synthesized from captured traffic. */
26
26
  export function buildHttpParams(provider) {
27
27
  // A blank egress is treated as unset: omit it so the local proof uses the
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The ONE tool primitive for the agent. A tool is authored once with
3
+ * {@link defineTool} — `{ definition, handler }`, where the handler receives
4
+ * args already parsed + validated against the definition's JSON Schema (via
5
+ * zod), so the schema shown to the model and the validation applied to inbound
6
+ * args cannot drift.
7
+ *
8
+ * This lives outside `mcp/` on purpose: it depends only on zod (NOT the MCP
9
+ * SDK), so non-MCP consumers — the cloud authoring loop — can author and run
10
+ * these tools without pulling in `@modelcontextprotocol/sdk`. The MCP-specific
11
+ * glue (`registerToolList` onto an `McpServer`) stays in `mcp/server.ts`, which
12
+ * re-exports this primitive for the MCP-side tool files.
13
+ */
14
+ import type { JSONSchema } from 'zod/v4/core';
15
+ export interface ToolDefinition {
16
+ name: string;
17
+ description: string;
18
+ inputSchema: JSONSchema.ObjectSchema;
19
+ }
20
+ export interface RegisteredTool {
21
+ definition: ToolDefinition;
22
+ /**
23
+ * @param args Tool arguments (already zod-validated against the schema).
24
+ * @returns A JSON-stringifiable value to return to the LLM.
25
+ * @throws Any error; the host (MCP `registerToolList` / the cloud loop)
26
+ * catches it and surfaces it as an error tool-result.
27
+ */
28
+ handler: (args: unknown) => Promise<unknown>;
29
+ }
30
+ /**
31
+ * Define a tool with a single inputSchema (JSON Schema) as the source of truth.
32
+ * The handler receives args already parsed and type-narrowed via a Zod schema
33
+ * derived from that same JSON Schema — so the description sent to the client
34
+ * and the validation applied to inbound args cannot drift.
35
+ */
36
+ export declare function defineTool<TArgs>(definition: ToolDefinition, handler: (args: TArgs) => Promise<unknown>): RegisteredTool;
@@ -0,0 +1,17 @@
1
+ import { toZod } from "../mcp/api/to-zod.js";
2
+ /**
3
+ * Define a tool with a single inputSchema (JSON Schema) as the source of truth.
4
+ * The handler receives args already parsed and type-narrowed via a Zod schema
5
+ * derived from that same JSON Schema — so the description sent to the client
6
+ * and the validation applied to inbound args cannot drift.
7
+ */
8
+ export function defineTool(definition, handler) {
9
+ const schema = toZod(definition.inputSchema);
10
+ return {
11
+ definition,
12
+ handler: async (raw) => {
13
+ const args = schema.parse(raw);
14
+ return await handler(args);
15
+ },
16
+ };
17
+ }
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",
4
+ "version": "0.1.5",
5
5
  "description": "Reclaim Protocol agent — SDK, CLI, and MCP server in one package. SDK + CLI talk to the builder backend; MCP server adds Chrome CDP capture, provider synthesis, replay, and zkTLS proof.",
6
6
  "type": "module",
7
7
  "main": "./lib/index.js",