@reclaimprotocol/agent 0.1.0-dev.1

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.
Files changed (191) hide show
  1. package/README.md +287 -0
  2. package/lib/cdp/capture.d.ts +22 -0
  3. package/lib/cdp/capture.js +159 -0
  4. package/lib/cdp/client.d.ts +9 -0
  5. package/lib/cdp/client.js +11 -0
  6. package/lib/cdp/eviction.d.ts +16 -0
  7. package/lib/cdp/eviction.js +38 -0
  8. package/lib/cdp/launcher.d.ts +14 -0
  9. package/lib/cdp/launcher.js +102 -0
  10. package/lib/cdp/tabs.d.ts +6 -0
  11. package/lib/cdp/tabs.js +10 -0
  12. package/lib/cli/args.d.ts +14 -0
  13. package/lib/cli/args.js +19 -0
  14. package/lib/cli/commands.d.ts +1 -0
  15. package/lib/cli/commands.js +19 -0
  16. package/lib/cli.d.ts +1 -0
  17. package/lib/cli.js +7 -0
  18. package/lib/config/store.d.ts +45 -0
  19. package/lib/config/store.js +46 -0
  20. package/lib/index.d.ts +3 -0
  21. package/lib/index.js +7 -0
  22. package/lib/logger.d.ts +2 -0
  23. package/lib/logger.js +7 -0
  24. package/lib/mcp/api/operations.gen.d.ts +152 -0
  25. package/lib/mcp/api/operations.gen.js +2300 -0
  26. package/lib/mcp/api/tools.d.ts +3 -0
  27. package/lib/mcp/api/tools.js +36 -0
  28. package/lib/mcp/remap_errors.d.ts +2 -0
  29. package/lib/mcp/remap_errors.js +27 -0
  30. package/lib/mcp/server.d.ts +38 -0
  31. package/lib/mcp/server.js +76 -0
  32. package/lib/mcp/start.d.ts +1 -0
  33. package/lib/mcp/start.js +63 -0
  34. package/lib/mcp/tools/agent/analyze.d.ts +3 -0
  35. package/lib/mcp/tools/agent/analyze.js +26 -0
  36. package/lib/mcp/tools/agent/attach.d.ts +11 -0
  37. package/lib/mcp/tools/agent/attach.js +72 -0
  38. package/lib/mcp/tools/agent/build.d.ts +33 -0
  39. package/lib/mcp/tools/agent/build.js +74 -0
  40. package/lib/mcp/tools/agent/capture.d.ts +10 -0
  41. package/lib/mcp/tools/agent/capture.js +142 -0
  42. package/lib/mcp/tools/agent/eval.d.ts +5 -0
  43. package/lib/mcp/tools/agent/eval.js +127 -0
  44. package/lib/mcp/tools/agent/find.d.ts +3 -0
  45. package/lib/mcp/tools/agent/find.js +60 -0
  46. package/lib/mcp/tools/agent/proof.d.ts +3 -0
  47. package/lib/mcp/tools/agent/proof.js +214 -0
  48. package/lib/mcp/tools/agent/propose.d.ts +9 -0
  49. package/lib/mcp/tools/agent/propose.js +75 -0
  50. package/lib/mcp/tools/agent/publish.d.ts +3 -0
  51. package/lib/mcp/tools/agent/publish.js +122 -0
  52. package/lib/mcp/tools/agent/replay.d.ts +3 -0
  53. package/lib/mcp/tools/agent/replay.js +46 -0
  54. package/lib/mcp/tools/agent/session.d.ts +10 -0
  55. package/lib/mcp/tools/agent/session.js +29 -0
  56. package/lib/mcp/tools/credentials/authenticate.d.ts +3 -0
  57. package/lib/mcp/tools/credentials/authenticate.js +108 -0
  58. package/lib/mcp/tools/credentials/build.d.ts +16 -0
  59. package/lib/mcp/tools/credentials/build.js +33 -0
  60. package/lib/mcp/tools/credentials/cache.d.ts +70 -0
  61. package/lib/mcp/tools/credentials/cache.js +114 -0
  62. package/lib/mcp/tools/credentials/env-file.d.ts +25 -0
  63. package/lib/mcp/tools/credentials/env-file.js +71 -0
  64. package/lib/mcp/tools/credentials/import.d.ts +2 -0
  65. package/lib/mcp/tools/credentials/import.js +52 -0
  66. package/lib/mcp/tools/credentials/issue.d.ts +2 -0
  67. package/lib/mcp/tools/credentials/issue.js +84 -0
  68. package/lib/mcp/tools/credentials/list-cached.d.ts +2 -0
  69. package/lib/mcp/tools/credentials/list-cached.js +55 -0
  70. package/lib/mcp/tools/credentials/register.d.ts +18 -0
  71. package/lib/mcp/tools/credentials/register.js +30 -0
  72. package/lib/mcp/tools/credentials/resolve-owner-key.d.ts +12 -0
  73. package/lib/mcp/tools/credentials/resolve-owner-key.js +68 -0
  74. package/lib/mcp/tools/credentials/session-cache.d.ts +14 -0
  75. package/lib/mcp/tools/credentials/session-cache.js +19 -0
  76. package/lib/mcp/tools/credentials/status.d.ts +3 -0
  77. package/lib/mcp/tools/credentials/status.js +62 -0
  78. package/lib/mcp-bin.d.ts +1 -0
  79. package/lib/mcp-bin.js +7 -0
  80. package/lib/old/client.d.ts +67 -0
  81. package/lib/old/client.js +146 -0
  82. package/lib/old/flag.d.ts +11 -0
  83. package/lib/old/flag.js +13 -0
  84. package/lib/old/identity.d.ts +22 -0
  85. package/lib/old/identity.js +47 -0
  86. package/lib/old/instructions.d.ts +7 -0
  87. package/lib/old/instructions.js +29 -0
  88. package/lib/old/login-cdp.d.ts +23 -0
  89. package/lib/old/login-cdp.js +125 -0
  90. package/lib/old/to-register.d.ts +47 -0
  91. package/lib/old/to-register.js +133 -0
  92. package/lib/old/tools/authenticate.d.ts +18 -0
  93. package/lib/old/tools/authenticate.js +127 -0
  94. package/lib/old/tools/build.d.ts +9 -0
  95. package/lib/old/tools/build.js +16 -0
  96. package/lib/old/tools/me-providers.d.ts +10 -0
  97. package/lib/old/tools/me-providers.js +32 -0
  98. package/lib/old/tools/publish.d.ts +16 -0
  99. package/lib/old/tools/publish.js +97 -0
  100. package/lib/paths.d.ts +11 -0
  101. package/lib/paths.js +32 -0
  102. package/lib/pkg.d.ts +3 -0
  103. package/lib/pkg.js +13 -0
  104. package/lib/proof/attestor.d.ts +39 -0
  105. package/lib/proof/attestor.js +207 -0
  106. package/lib/proof/replay.d.ts +8 -0
  107. package/lib/proof/replay.js +73 -0
  108. package/lib/provider/constraints.d.ts +2 -0
  109. package/lib/provider/constraints.js +122 -0
  110. package/lib/provider/draft.d.ts +6 -0
  111. package/lib/provider/draft.js +68 -0
  112. package/lib/provider/graphql.d.ts +2 -0
  113. package/lib/provider/graphql.js +35 -0
  114. package/lib/provider/matcher.d.ts +36 -0
  115. package/lib/provider/matcher.js +436 -0
  116. package/lib/provider/normalize.d.ts +4 -0
  117. package/lib/provider/normalize.js +51 -0
  118. package/lib/provider/schema.d.ts +76 -0
  119. package/lib/provider/schema.js +1 -0
  120. package/lib/provider/secrets.d.ts +5 -0
  121. package/lib/provider/secrets.js +22 -0
  122. package/lib/provider/to-version.d.ts +22 -0
  123. package/lib/provider/to-version.js +68 -0
  124. package/lib/types/openapi.d.ts +4 -0
  125. package/lib/types/openapi.js +1 -0
  126. package/package.json +88 -0
  127. package/scripts/download-zk-files.ts +42 -0
  128. package/scripts/prepend-shebang.ts +17 -0
  129. package/skill/SKILL.md +248 -0
  130. package/src/cdp/capture.ts +215 -0
  131. package/src/cdp/client.ts +20 -0
  132. package/src/cdp/eviction.ts +52 -0
  133. package/src/cdp/launcher.ts +133 -0
  134. package/src/cdp/tabs.ts +22 -0
  135. package/src/cli/args.ts +20 -0
  136. package/src/cli/commands.ts +27 -0
  137. package/src/cli.ts +7 -0
  138. package/src/config/store.ts +86 -0
  139. package/src/index.ts +12 -0
  140. package/src/logger.ts +8 -0
  141. package/src/mcp/api/operations.gen.ts +2397 -0
  142. package/src/mcp/api/tools.ts +45 -0
  143. package/src/mcp/remap_errors.ts +35 -0
  144. package/src/mcp/server.ts +119 -0
  145. package/src/mcp/start.ts +78 -0
  146. package/src/mcp/tools/agent/analyze.ts +34 -0
  147. package/src/mcp/tools/agent/attach.ts +95 -0
  148. package/src/mcp/tools/agent/build.ts +99 -0
  149. package/src/mcp/tools/agent/capture.ts +202 -0
  150. package/src/mcp/tools/agent/eval.ts +161 -0
  151. package/src/mcp/tools/agent/find.ts +73 -0
  152. package/src/mcp/tools/agent/proof.ts +272 -0
  153. package/src/mcp/tools/agent/propose.ts +111 -0
  154. package/src/mcp/tools/agent/publish.ts +155 -0
  155. package/src/mcp/tools/agent/replay.ts +58 -0
  156. package/src/mcp/tools/agent/session.ts +46 -0
  157. package/src/mcp/tools/credentials/authenticate.ts +135 -0
  158. package/src/mcp/tools/credentials/build.ts +37 -0
  159. package/src/mcp/tools/credentials/cache.ts +161 -0
  160. package/src/mcp/tools/credentials/env-file.ts +101 -0
  161. package/src/mcp/tools/credentials/import.ts +72 -0
  162. package/src/mcp/tools/credentials/issue.ts +107 -0
  163. package/src/mcp/tools/credentials/list-cached.ts +66 -0
  164. package/src/mcp/tools/credentials/register.ts +49 -0
  165. package/src/mcp/tools/credentials/resolve-owner-key.ts +78 -0
  166. package/src/mcp/tools/credentials/session-cache.ts +31 -0
  167. package/src/mcp/tools/credentials/status.ts +82 -0
  168. package/src/mcp-bin.ts +7 -0
  169. package/src/old/client.ts +236 -0
  170. package/src/old/flag.ts +13 -0
  171. package/src/old/identity.ts +56 -0
  172. package/src/old/instructions.ts +30 -0
  173. package/src/old/login-cdp.ts +153 -0
  174. package/src/old/to-register.ts +234 -0
  175. package/src/old/tools/authenticate.ts +152 -0
  176. package/src/old/tools/build.ts +21 -0
  177. package/src/old/tools/me-providers.ts +42 -0
  178. package/src/old/tools/publish.ts +135 -0
  179. package/src/paths.ts +37 -0
  180. package/src/pkg.ts +24 -0
  181. package/src/proof/attestor.ts +286 -0
  182. package/src/proof/replay.ts +102 -0
  183. package/src/provider/constraints.ts +150 -0
  184. package/src/provider/draft.ts +105 -0
  185. package/src/provider/graphql.ts +42 -0
  186. package/src/provider/matcher.ts +616 -0
  187. package/src/provider/normalize.ts +65 -0
  188. package/src/provider/schema.ts +92 -0
  189. package/src/provider/secrets.ts +29 -0
  190. package/src/provider/to-version.ts +90 -0
  191. package/src/types/openapi.ts +7 -0
@@ -0,0 +1,97 @@
1
+ import { defineTool } from "../../mcp/server.js";
2
+ import { bumpPatchString, extractVersions, providerToConfigBody, providerToRegisterBody, } from "../to-register.js";
3
+ const DEFAULT_VERSION_INFO = 'Updated from MCP browser capture';
4
+ /**
5
+ * Publish a drafted provider to the OLD devtools backend. Keeps the builder
6
+ * tool NAME (`create_provider_version_from_capture`) so the skill flow is
7
+ * unchanged, but the behaviour differs:
8
+ *
9
+ * - WITHOUT `providerId` → `POST /api/providers/register`: creates a
10
+ * brand-new provider + version 1.0.0. Re-running creates a DUPLICATE.
11
+ * - WITH `providerId` → `POST /api/providers/:providerId/config`: adds a
12
+ * new version to an existing provider (auto-bumps the patch above the
13
+ * highest existing version).
14
+ *
15
+ * Get a `providerId` from `get_me_providers`.
16
+ */
17
+ export function createVersionFromCaptureTool(client) {
18
+ return defineTool({
19
+ name: 'create_provider_version_from_capture',
20
+ description: 'Publish a drafted ReclaimProvider to the OLD devtools backend. ' +
21
+ 'Pass the `provider` object from propose_provider VERBATIM and the ' +
22
+ '`initialUrl` the user landed on (the login/landing page, NOT the ' +
23
+ 'API url). \n\n' +
24
+ 'Omit `providerId` to REGISTER A NEW provider (creates it + version ' +
25
+ '1.0.0; re-running creates a duplicate). Pass `providerId` (from ' +
26
+ 'get_me_providers) to add a NEW VERSION to that existing provider ' +
27
+ '(the patch is auto-bumped). \n\n' +
28
+ 'Captured requests must be GET/POST. OPRF redactions are supported.',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: {
32
+ provider: {
33
+ type: 'object',
34
+ description: 'The `provider` object from propose_provider, verbatim.',
35
+ properties: {
36
+ name: { type: 'string' },
37
+ url: { type: 'string' },
38
+ method: { type: 'string' },
39
+ headers: { type: 'object' },
40
+ body: { type: 'string' },
41
+ responseMatches: { type: 'array' },
42
+ responseRedactions: { type: 'array' },
43
+ paramValues: { type: 'object' },
44
+ },
45
+ required: ['name', 'url', 'method', 'responseMatches'],
46
+ },
47
+ initialUrl: {
48
+ type: 'string',
49
+ description: 'The website URL the user landed on to trigger the request ' +
50
+ '(maps to the provider `loginUrl`).',
51
+ },
52
+ providerId: {
53
+ type: 'string',
54
+ description: 'Existing provider to add a new version to. Omit to register ' +
55
+ 'a brand-new provider.',
56
+ },
57
+ description: {
58
+ type: 'string',
59
+ description: 'Provider description (new-provider register only).',
60
+ },
61
+ geoLocation: { type: 'string' },
62
+ providerType: { type: 'string', enum: ['PRIVATE', 'PUBLIC'] },
63
+ notes: {
64
+ type: 'string',
65
+ description: 'Change note for the new version (update only; must be ≥10 ' +
66
+ 'chars or a default is used).',
67
+ },
68
+ },
69
+ required: ['provider', 'initialUrl'],
70
+ },
71
+ }, async (args) => {
72
+ const opts = { initialUrl: args.initialUrl };
73
+ if (args.description !== undefined) {
74
+ opts.description = args.description;
75
+ }
76
+ if (args.geoLocation !== undefined) {
77
+ opts.geoLocation = args.geoLocation;
78
+ }
79
+ if (args.providerType !== undefined) {
80
+ opts.providerType = args.providerType;
81
+ }
82
+ // New provider.
83
+ if (!args.providerId) {
84
+ const body = providerToRegisterBody(args.provider, opts);
85
+ return client.registerProvider(body);
86
+ }
87
+ // New version of an existing provider — pick the next patch above
88
+ // the highest existing version.
89
+ const versionsResp = await client.listVersions(args.providerId);
90
+ const version = bumpPatchString(extractVersions(versionsResp));
91
+ const versionInfo = args.notes && args.notes.trim().length >= 10
92
+ ? args.notes.trim()
93
+ : DEFAULT_VERSION_INFO;
94
+ const body = providerToConfigBody(args.provider, opts, version, versionInfo);
95
+ return client.updateProviderConfig(args.providerId, body);
96
+ });
97
+ }
package/lib/paths.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Resolve the Reclaim cache directory (`~/.reclaim`). Honours the
3
+ * `RECLAIM_HOME` env var as an override so tests can sandbox writes.
4
+ */
5
+ export declare function reclaimHomeDirPath(): string;
6
+ /**
7
+ * Walk up from `start` until we find a directory containing one of
8
+ * the project-root markers, then return that directory. Falls back
9
+ * to `start` if no marker is found within 8 levels.
10
+ */
11
+ export declare function findProjectRoot(start?: string): string;
package/lib/paths.js ADDED
@@ -0,0 +1,32 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ /**
5
+ * Resolve the Reclaim cache directory (`~/.reclaim`). Honours the
6
+ * `RECLAIM_HOME` env var as an override so tests can sandbox writes.
7
+ */
8
+ export function reclaimHomeDirPath() {
9
+ return process.env.RECLAIM_HOME || join(homedir(), '.reclaim');
10
+ }
11
+ /**
12
+ * Walk up from `start` until we find a directory containing one of
13
+ * the project-root markers, then return that directory. Falls back
14
+ * to `start` if no marker is found within 8 levels.
15
+ */
16
+ export function findProjectRoot(start = process.cwd()) {
17
+ const markers = ['.git', 'package.json', 'pyproject.toml', 'Cargo.toml'];
18
+ let dir = resolve(start);
19
+ for (let i = 0; i < 8; i++) {
20
+ for (const m of markers) {
21
+ if (existsSync(join(dir, m))) {
22
+ return dir;
23
+ }
24
+ }
25
+ const parent = dirname(dir);
26
+ if (parent === dir) {
27
+ break;
28
+ }
29
+ dir = parent;
30
+ }
31
+ return start;
32
+ }
package/lib/pkg.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare const NAME: string;
2
+ export declare const VERSION: string;
3
+ export declare const DESCRIPTION: string;
package/lib/pkg.js ADDED
@@ -0,0 +1,13 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ // package.json lives outside `rootDir: src`, so we can't `import` it
5
+ // directly without widening rootDir and corrupting the build output
6
+ // tree. Read it at runtime instead — relative to this module's URL so
7
+ // the path resolves identically in dev (src/pkg.ts) and after build
8
+ // (build/pkg.js), both one level below the package root.
9
+ const PKG_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
10
+ const pkg = JSON.parse(readFileSync(PKG_PATH, 'utf8'));
11
+ export const NAME = pkg.name;
12
+ export const VERSION = pkg.version;
13
+ export const DESCRIPTION = pkg.description || '';
@@ -0,0 +1,39 @@
1
+ import type { ReclaimProvider } from '../provider/schema.ts';
2
+ export interface AttestorLogEntry {
3
+ level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
4
+ msg: string;
5
+ ctx?: Record<string, unknown>;
6
+ }
7
+ export interface ProofResult {
8
+ proof: unknown;
9
+ extractedValue: string;
10
+ extractedParameters: Record<string, string>;
11
+ identifier?: string;
12
+ owner?: string;
13
+ verified: boolean;
14
+ error?: string;
15
+ /** Set to `'missing-owner-key'` when no credential was available to
16
+ * sign with — distinguishes this recoverable case from other proof
17
+ * errors so the skill can point the dev at `issue_credentials` /
18
+ * `list_cached_credentials` rather than guessing. */
19
+ errorKind?: 'missing-owner-key';
20
+ /** Pino-style log entries captured from the attestor SDK during the
21
+ * proof attempt. Full stream on the result; the MCP layer trims it
22
+ * for the LLM-visible response and dumps the whole thing to disk
23
+ * via `runLogPath`. */
24
+ attestorLogs?: AttestorLogEntry[];
25
+ /** The exact `params` object passed to `createClaimOnAttestor` for
26
+ * this attempt. Only included on error so the LLM/dev can confirm
27
+ * what the attestor actually saw vs what they thought they sent
28
+ * (catches mutation bugs / template-substitution surprises). Never
29
+ * carries the captured secrets — those live in `secretParams` which
30
+ * is not echoed here. */
31
+ sentParams?: unknown;
32
+ }
33
+ /** Trim a captured log stream to the last N entries — for shrinking the
34
+ * LLM-visible response while the full stream stays in the on-disk dump. */
35
+ export declare function tailLogs(entries: AttestorLogEntry[], n?: number): AttestorLogEntry[];
36
+ /** Filter to just warn/error/fatal — for the LLM-visible response on
37
+ * successful proofs, where chatty trace/info would just waste context. */
38
+ export declare function warnsAndErrors(entries: AttestorLogEntry[]): AttestorLogEntry[];
39
+ export declare function runAttestorProof(provider: ReclaimProvider, secrets: Record<string, string>, attestorUrl?: string, ownerAddress?: string): Promise<ProofResult>;
@@ -0,0 +1,207 @@
1
+ // src/proof/attestor.ts
2
+ //
3
+ // Adapter over @reclaimprotocol/attestor-core's createClaimOnAttestor.
4
+ //
5
+ // responseMatches are the regex/contains checks the attestor runs on the
6
+ // revealed slice; jsonPath/xPath live on responseRedactions (what the prover
7
+ // reveals), never on responseMatches.
8
+ import { createClaimOnAttestor, } from '@reclaimprotocol/attestor-core';
9
+ import { resolveOwnerPrivateKey } from "../mcp/tools/credentials/cache.js";
10
+ /** Trim a captured log stream to the last N entries — for shrinking the
11
+ * LLM-visible response while the full stream stays in the on-disk dump. */
12
+ export function tailLogs(entries, n = LOG_TAIL_LINES) {
13
+ if (entries.length <= n) {
14
+ return entries;
15
+ }
16
+ return entries.slice(entries.length - n);
17
+ }
18
+ /** Filter to just warn/error/fatal — for the LLM-visible response on
19
+ * successful proofs, where chatty trace/info would just waste context. */
20
+ export function warnsAndErrors(entries) {
21
+ return entries.filter((e) => e.level === 'warn' || e.level === 'error' || e.level === 'fatal');
22
+ }
23
+ /** How many log entries to keep from the attestor SDK on an error. The
24
+ * early ones describe TLS setup; the late ones (right before the
25
+ * throw) usually pinpoint the actual failure. Keeping the tail covers
26
+ * the latter; the cap stops a chatty session from blowing the LLM's
27
+ * context window. */
28
+ const LOG_TAIL_LINES = 80;
29
+ /** Build a pino-compatible capturing logger. Each `trace`/`debug`/…
30
+ * call records an entry; `child` returns a derived capturer bound to
31
+ * the parent's entries array so nested SDK loggers all flow into one
32
+ * ordered list. */
33
+ function makeCaptureLogger() {
34
+ const entries = [];
35
+ const make = (baseCtx) => {
36
+ const log = (level) => (...args) => {
37
+ if (args.length === 0) {
38
+ return;
39
+ }
40
+ let msg = '';
41
+ let ctx;
42
+ if (args[0] instanceof Error) {
43
+ // pino-style `logger.error(err, 'msg')` — Error objects
44
+ // don't JSON-serialize on their own, so extract the
45
+ // useful fields manually.
46
+ const err = args[0];
47
+ ctx = {
48
+ error: { name: err.name, message: err.message, stack: err.stack },
49
+ };
50
+ msg = typeof args[1] === 'string' ? args[1] : err.message;
51
+ }
52
+ else if (typeof args[0] === 'string') {
53
+ msg = args[0];
54
+ }
55
+ else if (args[0] && typeof args[0] === 'object') {
56
+ ctx = args[0];
57
+ msg = typeof args[1] === 'string' ? args[1] : '';
58
+ }
59
+ const merged = Object.keys(baseCtx).length || ctx
60
+ ? { ...baseCtx, ...(ctx ?? {}) }
61
+ : undefined;
62
+ const entry = { level, msg };
63
+ if (merged) {
64
+ entry.ctx = merged;
65
+ }
66
+ entries.push(entry);
67
+ };
68
+ return {
69
+ trace: log('trace'),
70
+ debug: log('debug'),
71
+ info: log('info'),
72
+ warn: log('warn'),
73
+ error: log('error'),
74
+ fatal: log('fatal'),
75
+ child: (childCtx) => {
76
+ return make({ ...baseCtx, ...childCtx });
77
+ },
78
+ };
79
+ };
80
+ return { logger: make({}), entries };
81
+ }
82
+ /** Default public attestor websocket URL. Resolved per-call so an env-var
83
+ * change or per-request override takes effect without restarting the
84
+ * MCP server. */
85
+ const FALLBACK_ATTESTOR_URL = 'wss://attestor.reclaimprotocol.org:444/ws';
86
+ function resolveAttestorUrl(override) {
87
+ return (override ?? process.env['RECLAIM_ATTESTOR_URL'] ?? FALLBACK_ATTESTOR_URL);
88
+ }
89
+ // ---------------------------------------------------------------------------
90
+ // Main entry point
91
+ // ---------------------------------------------------------------------------
92
+ export async function runAttestorProof(provider, secrets, attestorUrl, ownerAddress) {
93
+ // The owner is chosen up front (resolve_owner_key) and passed as
94
+ // `ownerAddress`; here we just fetch its private key and sign.
95
+ const ownerPrivateKey = ownerAddress
96
+ ? resolveOwnerPrivateKey(ownerAddress)
97
+ : undefined;
98
+ if (!ownerPrivateKey) {
99
+ return {
100
+ proof: undefined,
101
+ extractedValue: '',
102
+ extractedParameters: {},
103
+ verified: false,
104
+ error: `No private key available for ownerAddress "${ownerAddress ?? ''}". ` +
105
+ 'Call resolve_owner_key to pick a credential (or ' +
106
+ 'issue_credentials / import_credentials to set one up), then ' +
107
+ 'retry run_proof with its address as ownerAddress.',
108
+ errorKind: 'missing-owner-key',
109
+ };
110
+ }
111
+ try {
112
+ const params = {
113
+ url: provider.url,
114
+ method: provider.method,
115
+ ...(provider.headers !== undefined && { headers: provider.headers }),
116
+ ...(provider.body !== undefined && { body: provider.body }),
117
+ responseMatches: provider.responseMatches,
118
+ responseRedactions: provider.responseRedactions ?? [],
119
+ ...(provider.paramValues !== undefined && {
120
+ paramValues: provider.paramValues,
121
+ }),
122
+ };
123
+ // Diagnostic logger capture is gated behind RECLAIM_AGENT_TRACE so the
124
+ // happy path doesn't pay the cost (the attestor SDK emits a *lot* of
125
+ // trace/debug entries during ZK setup). Flip RECLAIM_AGENT_TRACE=1 to
126
+ // turn it back on when something's broken; logs flow into the proof
127
+ // response and into the on-disk proof-runs dump.
128
+ const traceEnabled = process.env['RECLAIM_AGENT_TRACE'] === '1';
129
+ const capture = traceEnabled ? makeCaptureLogger() : undefined;
130
+ const attestorLogs = capture?.entries;
131
+ let claim;
132
+ try {
133
+ claim = await createClaimOnAttestor({
134
+ name: 'http',
135
+ params,
136
+ secretParams: {
137
+ headers: secrets,
138
+ },
139
+ ownerPrivateKey,
140
+ client: { url: resolveAttestorUrl(attestorUrl) },
141
+ zkEngine: 'snarkjs',
142
+ ...(capture !== undefined && { logger: capture.logger }),
143
+ });
144
+ }
145
+ catch (err) {
146
+ const errorResult = {
147
+ proof: undefined,
148
+ extractedValue: '',
149
+ extractedParameters: {},
150
+ verified: false,
151
+ error: err instanceof Error ? err.message : String(err),
152
+ sentParams: params,
153
+ };
154
+ if (attestorLogs !== undefined) {
155
+ errorResult.attestorLogs = attestorLogs;
156
+ }
157
+ return errorResult;
158
+ }
159
+ // ClaimTunnelResponse carries the claim under `.claim`.
160
+ const claimRecord = claim;
161
+ const claimData = claimRecord['claim'];
162
+ // claimData.context is a JSON-encoded string carrying
163
+ // { extractedParameters: {...}, providerHash, ... }.
164
+ let context = {};
165
+ const rawContext = claimData?.['context'];
166
+ if (typeof rawContext === 'string') {
167
+ try {
168
+ context = JSON.parse(rawContext);
169
+ }
170
+ catch {
171
+ // Leave context empty if it isn't valid JSON.
172
+ }
173
+ }
174
+ const extractedParameters = context['extractedParameters'] ??
175
+ {};
176
+ const extractedValue = Object.values(extractedParameters)[0] ?? '';
177
+ const result = {
178
+ proof: claim,
179
+ extractedValue,
180
+ extractedParameters,
181
+ verified: true,
182
+ };
183
+ const identifier = claimData?.['identifier'];
184
+ if (typeof identifier === 'string') {
185
+ result.identifier = identifier;
186
+ }
187
+ const owner = claimData?.['owner'];
188
+ if (typeof owner === 'string') {
189
+ result.owner = owner;
190
+ }
191
+ // Only set when RECLAIM_AGENT_TRACE=1. Full stream — the MCP layer
192
+ // trims for the LLM-visible response; the on-disk dump gets it all.
193
+ if (attestorLogs !== undefined && attestorLogs.length > 0) {
194
+ result.attestorLogs = attestorLogs;
195
+ }
196
+ return result;
197
+ }
198
+ catch (err) {
199
+ return {
200
+ proof: undefined,
201
+ extractedValue: '',
202
+ extractedParameters: {},
203
+ verified: false,
204
+ error: err instanceof Error ? err.message : String(err),
205
+ };
206
+ }
207
+ }
@@ -0,0 +1,8 @@
1
+ import type { ReclaimProvider } from '../provider/schema.ts';
2
+ export interface ReplayResult {
3
+ status: number;
4
+ body: string;
5
+ matchesOriginal: boolean;
6
+ hints?: string[];
7
+ }
8
+ export declare function replayProvider(provider: ReclaimProvider, secrets: Record<string, string>): Promise<ReplayResult>;
@@ -0,0 +1,73 @@
1
+ /** Expand `{{name}}` placeholders from `paramValues`. Matches the attestor
2
+ * SDK's behavior so replay and proof both hit the same final URL. */
3
+ function expandTemplate(s, params) {
4
+ if (!params) {
5
+ return s;
6
+ }
7
+ return s.replace(/\{\{([^{}]+)\}\}/g, (match, name) => {
8
+ const v = params[name];
9
+ return v !== undefined ? v : match;
10
+ });
11
+ }
12
+ export async function replayProvider(provider, secrets) {
13
+ const params = provider.paramValues;
14
+ const url = expandTemplate(provider.url, params);
15
+ const expandedHeaders = {};
16
+ for (const [k, v] of Object.entries(provider.headers ?? {})) {
17
+ expandedHeaders[k] = expandTemplate(v, params);
18
+ }
19
+ const headers = { ...expandedHeaders, ...secrets };
20
+ const fetchInit = {
21
+ method: provider.method,
22
+ headers,
23
+ redirect: 'manual',
24
+ };
25
+ if (provider.body !== undefined) {
26
+ fetchInit.body = expandTemplate(provider.body, params);
27
+ }
28
+ const res = await fetch(url, fetchInit);
29
+ const body = await res.text();
30
+ const hints = [];
31
+ if (res.status === 401 || res.status === 403) {
32
+ hints.push('auth/permission failed');
33
+ }
34
+ if (res.status >= 500) {
35
+ hints.push('upstream server error');
36
+ }
37
+ for (const m of provider.responseMatches) {
38
+ // The attestor substitutes paramValues into responseMatches.value
39
+ // at proof time (`{{name}}` → the value, or → the OPRF nullifier
40
+ // when the matching redaction has `hash`). Replay can't OPRF, but
41
+ // it can do the plain substitution so a paramValues-templated match
42
+ // is meaningful here.
43
+ const expanded = {
44
+ ...m,
45
+ value: expandTemplate(m.value, params),
46
+ };
47
+ if (!matchesBody(body, expanded)) {
48
+ if (res.status >= 200 && res.status < 300) {
49
+ hints.push(`matcher ${expanded.type} did not match — ` +
50
+ 'value may have changed or matcher is too tight');
51
+ }
52
+ const result = {
53
+ status: res.status,
54
+ body,
55
+ matchesOriginal: false,
56
+ };
57
+ if (hints.length > 0) {
58
+ result.hints = hints;
59
+ }
60
+ return result;
61
+ }
62
+ }
63
+ return { status: res.status, body, matchesOriginal: res.status < 400 };
64
+ }
65
+ function matchesBody(body, m) {
66
+ if (m.type === 'regex') {
67
+ return new RegExp(m.value).test(body);
68
+ }
69
+ if (m.type === 'contains') {
70
+ return body.includes(m.value);
71
+ }
72
+ return false;
73
+ }
@@ -0,0 +1,2 @@
1
+ import type { CapturedRequest, RequestConcern } from './schema.ts';
2
+ export declare function detectConcerns(req: CapturedRequest): RequestConcern[];
@@ -0,0 +1,122 @@
1
+ import { looksLikeGraphqlEndpoint } from "./graphql.js";
2
+ const SIGNATURE_HEADERS = [
3
+ 'x-signature',
4
+ 'x-amz-signature',
5
+ 'x-hub-signature',
6
+ 'x-hmac',
7
+ 'signature',
8
+ 'x-request-signature',
9
+ ];
10
+ const CSRF_HEADERS = [
11
+ 'x-csrf-token',
12
+ 'x-xsrf-token',
13
+ 'csrf-token',
14
+ 'xsrf-token',
15
+ ];
16
+ export function detectConcerns(req) {
17
+ const concerns = [];
18
+ const reqHeadersLower = lowerKeys(req.requestHeaders);
19
+ const respHeadersLower = lowerKeys(req.responseHeaders);
20
+ // Signed requests
21
+ for (const h of SIGNATURE_HEADERS) {
22
+ if (reqHeadersLower[h]) {
23
+ concerns.push({
24
+ code: 'signedRequest',
25
+ message: `Request includes a "${h}" header — ` +
26
+ 'providers will break when the signing scheme rotates.',
27
+ });
28
+ break;
29
+ }
30
+ }
31
+ // Bot challenge
32
+ const cfMitigated = respHeadersLower['cf-mitigated'];
33
+ const server = respHeadersLower['server'] ?? '';
34
+ if ((req.status === 403 || req.status === 503) &&
35
+ (cfMitigated === 'challenge' || /cloudflare/i.test(server))) {
36
+ concerns.push({
37
+ code: 'botChallenge',
38
+ message: 'Response looks like a bot-mitigation challenge (Cloudflare). ' +
39
+ 'Capture again after solving the challenge in the browser.',
40
+ });
41
+ }
42
+ // GraphQL
43
+ if (looksLikeGraphqlEndpoint(req.url)) {
44
+ concerns.push({
45
+ code: 'graphql',
46
+ message: 'Endpoint is GraphQL — ' +
47
+ 'match on operationName + variables, not URL path.',
48
+ });
49
+ }
50
+ // CSRF dependency
51
+ for (const h of CSRF_HEADERS) {
52
+ if (reqHeadersLower[h]) {
53
+ concerns.push({
54
+ code: 'csrfDependency',
55
+ message: `Request includes a "${h}" header — ` +
56
+ 'a prior request fetched this token. ' +
57
+ 'Replay will fail unless the token is still valid; ' +
58
+ 'expect to re-capture for proof.',
59
+ });
60
+ break;
61
+ }
62
+ }
63
+ // Short-lived token (JWT exp < 30 min from finishedAt)
64
+ const auth = reqHeadersLower['authorization'];
65
+ if (auth) {
66
+ const jwtMatch = auth.match(/^[Bb]earer\s+([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/);
67
+ if (jwtMatch) {
68
+ const exp = jwtExpSeconds(jwtMatch[1]);
69
+ const issuedAtMs = req.finishedAt || Date.now();
70
+ if (exp && exp * 1000 - issuedAtMs < 30 * 60 * 1000) {
71
+ concerns.push({
72
+ code: 'shortLivedToken',
73
+ message: 'Authorization is a JWT expiring in under 30 minutes — ' +
74
+ 'provider may stop working soon. Re-capture closer to proof time.',
75
+ });
76
+ }
77
+ }
78
+ }
79
+ // Encrypted / binary body
80
+ if (looksBinary(req.contentType, req.responseBody)) {
81
+ concerns.push({
82
+ code: 'encryptedBody',
83
+ message: `Response content-type "${req.contentType}" is binary/opaque —` +
84
+ ' Reclaim providers need plain-text (JSON/HTML) responses.',
85
+ });
86
+ }
87
+ return concerns;
88
+ }
89
+ function lowerKeys(h) {
90
+ return Object.fromEntries(Object.entries(h).map(([k, v]) => [k.toLowerCase(), v]));
91
+ }
92
+ function jwtExpSeconds(jwt) {
93
+ const parts = jwt.split('.');
94
+ if (parts.length !== 3) {
95
+ return undefined;
96
+ }
97
+ try {
98
+ const decoded = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
99
+ return typeof decoded.exp === 'number' ? decoded.exp : undefined;
100
+ }
101
+ catch {
102
+ return undefined;
103
+ }
104
+ }
105
+ function looksBinary(contentType, body) {
106
+ if (!body) {
107
+ return false;
108
+ }
109
+ if (/octet-stream|protobuf|msgpack|cbor/i.test(contentType)) {
110
+ return true;
111
+ }
112
+ // Heuristic: significant control-char density in first 256 bytes.
113
+ const sample = body.slice(0, 256);
114
+ let ctrl = 0;
115
+ for (const ch of sample) {
116
+ const code = ch.charCodeAt(0);
117
+ if (code < 9 || (code > 13 && code < 32)) {
118
+ ctrl++;
119
+ }
120
+ }
121
+ return ctrl / sample.length > 0.1;
122
+ }
@@ -0,0 +1,6 @@
1
+ import type { CapturedRequest, OprfMode, ReclaimProvider, SecretParamRef } from './schema.ts';
2
+ export interface DraftResult {
3
+ provider: ReclaimProvider;
4
+ secretRefs: SecretParamRef[];
5
+ }
6
+ export declare function draftProvider(req: CapturedRequest, target: string, name: string, hash?: OprfMode): DraftResult;