@reclaimprotocol/agent 0.1.17 → 0.1.20

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 (171) hide show
  1. package/README.md +6 -2
  2. package/lib/authoring/backend.d.ts +4 -0
  3. package/lib/authoring/tools.js +4 -1
  4. package/lib/cdp/capture.d.ts +2 -2
  5. package/lib/cdp/capture.js +25 -21
  6. package/lib/cdp/client.d.ts +39 -1
  7. package/lib/cdp/client.js +125 -1
  8. package/lib/consts.d.ts +7 -0
  9. package/lib/consts.js +15 -0
  10. package/lib/mcp/api/operations.gen.js +33 -9
  11. package/lib/mcp/instructions.js +27 -3
  12. package/lib/mcp/start.js +12 -8
  13. package/lib/mcp/tools/agent/attach.d.ts +18 -4
  14. package/lib/mcp/tools/agent/attach.js +142 -52
  15. package/lib/mcp/tools/agent/build.d.ts +1 -1
  16. package/lib/mcp/tools/agent/build.js +5 -3
  17. package/lib/mcp/tools/agent/capture.d.ts +1 -0
  18. package/lib/mcp/tools/agent/capture.js +8 -1
  19. package/lib/mcp/tools/agent/eval.js +2 -3
  20. package/lib/mcp/tools/agent/how-it-works.d.ts +1 -1
  21. package/lib/mcp/tools/agent/how-it-works.js +1 -1
  22. package/lib/mcp/tools/agent/publish.js +55 -1
  23. package/lib/mcp/tools/agent/session.d.ts +2 -2
  24. package/lib/mcp/tools/agent/session.js +21 -5
  25. package/lib/mcp/tools/agent/shared.js +4 -6
  26. package/lib/mcp/tools/authenticate/authenticate.d.ts +1 -1
  27. package/lib/mcp/tools/authenticate/authenticate.js +10 -4
  28. package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -0
  29. package/lib/mcp/tools/authenticate/builder-access.js +31 -0
  30. package/lib/mcp/tools/authenticate/index.d.ts +1 -0
  31. package/lib/mcp/tools/authenticate/index.js +1 -0
  32. package/lib/mcp/tools/mode/mode.d.ts +3 -2
  33. package/lib/mcp/tools/mode/mode.js +24 -4
  34. package/lib/old/login-cdp.js +11 -3
  35. package/lib/old/to-register.d.ts +16 -8
  36. package/lib/old/to-register.js +29 -21
  37. package/lib/old/tools/publish.js +109 -9
  38. package/lib/provider/draft.d.ts +20 -0
  39. package/lib/provider/draft.js +75 -51
  40. package/lib/provider/index.d.ts +1 -1
  41. package/lib/provider/index.js +1 -1
  42. package/lib/provider/injection-doc.js +4 -3
  43. package/lib/provider/schema.d.ts +26 -1
  44. package/lib/provider/secrets.d.ts +37 -0
  45. package/lib/provider/secrets.js +147 -6
  46. package/lib/provider/to-version.d.ts +26 -2
  47. package/lib/provider/to-version.js +29 -5
  48. package/package.json +2 -2
  49. package/skill/SKILL.builder.md +43 -95
  50. package/skill/SKILL.common.md +102 -0
  51. package/skill/SKILL.old.md +20 -74
  52. package/skill/builder-mode.md +4 -2
  53. package/skill/injection-api.md +95 -10
  54. package/skill/old-mode.md +5 -3
  55. package/src/authoring/backend.ts +4 -0
  56. package/src/authoring/tools.ts +4 -1
  57. package/src/cdp/capture.ts +24 -24
  58. package/src/cdp/client.ts +197 -2
  59. package/src/consts.ts +20 -0
  60. package/src/mcp/api/operations.gen.ts +33 -9
  61. package/src/mcp/instructions.ts +36 -3
  62. package/src/mcp/start.ts +13 -8
  63. package/src/mcp/tools/agent/attach.ts +198 -57
  64. package/src/mcp/tools/agent/build.ts +9 -2
  65. package/src/mcp/tools/agent/capture.ts +10 -1
  66. package/src/mcp/tools/agent/eval.ts +2 -3
  67. package/src/mcp/tools/agent/how-it-works.ts +1 -1
  68. package/src/mcp/tools/agent/publish.ts +64 -1
  69. package/src/mcp/tools/agent/session.ts +29 -8
  70. package/src/mcp/tools/agent/shared.ts +5 -10
  71. package/src/mcp/tools/authenticate/authenticate.ts +14 -4
  72. package/src/mcp/tools/authenticate/builder-access.ts +42 -0
  73. package/src/mcp/tools/authenticate/index.ts +4 -0
  74. package/src/mcp/tools/mode/mode.ts +28 -4
  75. package/src/old/login-cdp.ts +13 -3
  76. package/src/old/to-register.ts +50 -27
  77. package/src/old/tools/publish.ts +131 -9
  78. package/src/provider/draft.ts +98 -56
  79. package/src/provider/index.ts +9 -1
  80. package/src/provider/injection-doc.ts +4 -3
  81. package/src/provider/schema.ts +27 -1
  82. package/src/provider/secrets.ts +171 -6
  83. package/src/provider/to-version.ts +53 -7
  84. package/lib/authoring/find.d.ts +0 -3
  85. package/lib/authoring/find.js +0 -54
  86. package/lib/authoring/propose.d.ts +0 -3
  87. package/lib/authoring/propose.js +0 -109
  88. package/lib/authoring/replay.d.ts +0 -10
  89. package/lib/authoring/replay.js +0 -34
  90. package/lib/cli/args.d.ts +0 -14
  91. package/lib/cli/args.js +0 -19
  92. package/lib/cli/commands.d.ts +0 -1
  93. package/lib/cli/commands.js +0 -19
  94. package/lib/cli.d.ts +0 -1
  95. package/lib/cli.js +0 -7
  96. package/lib/mcp/credentials/authenticate.d.ts +0 -3
  97. package/lib/mcp/credentials/authenticate.js +0 -108
  98. package/lib/mcp/credentials/build.d.ts +0 -5
  99. package/lib/mcp/credentials/build.js +0 -18
  100. package/lib/mcp/credentials/cache.d.ts +0 -70
  101. package/lib/mcp/credentials/cache.js +0 -131
  102. package/lib/mcp/credentials/env-file.d.ts +0 -25
  103. package/lib/mcp/credentials/env-file.js +0 -71
  104. package/lib/mcp/credentials/import.d.ts +0 -2
  105. package/lib/mcp/credentials/import.js +0 -52
  106. package/lib/mcp/credentials/issue.d.ts +0 -2
  107. package/lib/mcp/credentials/issue.js +0 -84
  108. package/lib/mcp/credentials/keypair.d.ts +0 -17
  109. package/lib/mcp/credentials/keypair.js +0 -48
  110. package/lib/mcp/credentials/link.d.ts +0 -3
  111. package/lib/mcp/credentials/link.js +0 -107
  112. package/lib/mcp/credentials/list-cached.d.ts +0 -2
  113. package/lib/mcp/credentials/list-cached.js +0 -55
  114. package/lib/mcp/credentials/owner-key.d.ts +0 -23
  115. package/lib/mcp/credentials/owner-key.js +0 -58
  116. package/lib/mcp/credentials/paths.d.ts +0 -12
  117. package/lib/mcp/credentials/paths.js +0 -35
  118. package/lib/mcp/credentials/poll.d.ts +0 -27
  119. package/lib/mcp/credentials/poll.js +0 -61
  120. package/lib/mcp/credentials/register.d.ts +0 -18
  121. package/lib/mcp/credentials/register.js +0 -30
  122. package/lib/mcp/credentials/resolve-owner-key.d.ts +0 -12
  123. package/lib/mcp/credentials/resolve-owner-key.js +0 -68
  124. package/lib/mcp/credentials/session-cache.d.ts +0 -14
  125. package/lib/mcp/credentials/session-cache.js +0 -42
  126. package/lib/mcp/credentials/status.d.ts +0 -3
  127. package/lib/mcp/credentials/status.js +0 -76
  128. package/lib/mcp/register.d.ts +0 -38
  129. package/lib/mcp/register.js +0 -80
  130. package/lib/mcp/tools/analyze.d.ts +0 -3
  131. package/lib/mcp/tools/analyze.js +0 -26
  132. package/lib/mcp/tools/attach.d.ts +0 -11
  133. package/lib/mcp/tools/attach.js +0 -72
  134. package/lib/mcp/tools/capture.d.ts +0 -10
  135. package/lib/mcp/tools/capture.js +0 -142
  136. package/lib/mcp/tools/eval.d.ts +0 -5
  137. package/lib/mcp/tools/eval.js +0 -127
  138. package/lib/mcp/tools/find.d.ts +0 -3
  139. package/lib/mcp/tools/find.js +0 -60
  140. package/lib/mcp/tools/owner-key.d.ts +0 -2
  141. package/lib/mcp/tools/owner-key.js +0 -63
  142. package/lib/mcp/tools/proof.d.ts +0 -3
  143. package/lib/mcp/tools/proof.js +0 -214
  144. package/lib/mcp/tools/propose.d.ts +0 -9
  145. package/lib/mcp/tools/propose.js +0 -75
  146. package/lib/mcp/tools/publish.d.ts +0 -3
  147. package/lib/mcp/tools/publish.js +0 -59
  148. package/lib/mcp/tools/replay.d.ts +0 -3
  149. package/lib/mcp/tools/replay.js +0 -46
  150. package/lib/mcp/tools/session.d.ts +0 -10
  151. package/lib/mcp/tools/session.js +0 -29
  152. package/lib/mcp/tools/verification/diagnose.d.ts +0 -11
  153. package/lib/mcp/tools/verification/diagnose.js +0 -132
  154. package/lib/mcp/verifications/build.d.ts +0 -5
  155. package/lib/mcp/verifications/build.js +0 -14
  156. package/lib/mcp/verifications/create.d.ts +0 -3
  157. package/lib/mcp/verifications/create.js +0 -57
  158. package/lib/mcp/verifications/find.d.ts +0 -3
  159. package/lib/mcp/verifications/find.js +0 -37
  160. package/lib/mcp/verifications/get.d.ts +0 -3
  161. package/lib/mcp/verifications/get.js +0 -26
  162. package/lib/mcp/verifications/update.d.ts +0 -3
  163. package/lib/mcp/verifications/update.js +0 -78
  164. package/lib/old/tools/get-version-config.d.ts +0 -17
  165. package/lib/old/tools/get-version-config.js +0 -47
  166. package/lib/routes.gen.d.ts +0 -271
  167. package/lib/routes.gen.js +0 -73
  168. package/lib/types/openapi.d.ts +0 -4
  169. package/lib/types/openapi.gen.d.ts +0 -3967
  170. package/lib/types/openapi.gen.js +0 -5
  171. package/lib/types/openapi.js +0 -1
package/README.md CHANGED
@@ -65,7 +65,7 @@ or you can even ask it like:
65
65
  The agent orchestrates the tools for you:
66
66
 
67
67
  1. **`reclaim_authenticate`** — a Chrome window opens the Reclaim dashboard; you sign in. The token is captured automatically and the tab closes. (Or it reuses a still-valid cached identity.)
68
- 2. **`attach_chrome` + `navigate`** — opens the target site (e.g. `github.com`); **`wait_for_page`** waits while you log in there.
68
+ 2. **`attach_browser` + `navigate`** — uses local Chrome, a Builder-hosted remote browser, or a custom CDP websocket; **`wait_for_page`** waits while the operator logs in.
69
69
  3. **`start_capture` → `find_requests_containing` → `get_request`** — records network traffic and locates the request carrying your value.
70
70
  4. **`propose_provider`** — drafts the provider (a `contains` match + `jsonPath`/`xPath`/`regex` redaction), keeping any secret headers server-side.
71
71
  5. **`replay_request` → `run_proof`** — pre-flights the draft, then produces a verified zkTLS proof via the attestor.
@@ -83,7 +83,8 @@ These capture/synthesis/proof tools are backend-independent and registered in bo
83
83
 
84
84
  | Tool | Purpose |
85
85
  | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
86
- | `attach_chrome` | Connect to Chrome via CDP — dedicated profile or attach to running instance |
86
+ | `attach_browser` | Connect via local Chrome, Builder-hosted Popcorn with a shareable live view, or a custom CDP websocket |
87
+ | `attach_chrome` | Backward-compatible alias for `attach_browser` |
87
88
  | `list_tabs` | List open Chrome tabs |
88
89
  | `navigate`, `wait_for_page`, `eval_in_page` | Drive the tab; poll for login completion |
89
90
  | `start_capture`, `stop_capture` | Network capture lifecycle |
@@ -94,6 +95,9 @@ These capture/synthesis/proof tools are backend-independent and registered in bo
94
95
  | `replay_request` | Pre-flight replay via Node `fetch` (no attestor); also used for auth-bound check by omitting `secretParamsRef` |
95
96
  | `run_proof` | End-to-end zkTLS proof through the attestor SDK |
96
97
  | `create_provider_version_from_capture` | Publish a drafted provider to the backend |
98
+ | `authenticate_builder` | In old-devtools mode only, sign in to Builder for remote-browser allocation |
99
+ | `list_builder_organizations` | In old-devtools mode only, list organization ids for a quota-accounted Builder browser |
100
+ | `dispose_browser` | Stop captures, disconnect CDP, and immediately release a Builder-hosted browser |
97
101
  | `get_session`, `reset_session` | Session state |
98
102
 
99
103
  ## Backends
@@ -42,6 +42,10 @@ export interface ProveOptions {
42
42
  * its own result, passed straight through as the tool's output.
43
43
  */
44
44
  export interface AuthoringBackend {
45
+ /** Ask Chrome to reject page evaluation when side effects cannot be ruled
46
+ * out. Cloud authoring enables this; local MCP keeps its existing
47
+ * behavior. */
48
+ readonly readOnlyPageEval?: boolean;
45
49
  /** Send a raw CDP command scoped to this session's page. */
46
50
  sendCdp(method: string, params?: Record<string, unknown>): Promise<unknown>;
47
51
  /** Resolve once `method` (a CDP event) fires on this session's page, or
@@ -49,7 +49,10 @@ function globMatch(pattern, str) {
49
49
  }
50
50
  async function evalInPage(backend, expression) {
51
51
  const res = (await backend.sendCdp('Runtime.evaluate', {
52
- expression, returnByValue: true, awaitPromise: true,
52
+ expression,
53
+ returnByValue: true,
54
+ awaitPromise: true,
55
+ ...(backend.readOnlyPageEval ? { throwOnSideEffect: true } : {}),
53
56
  }));
54
57
  if (res.exceptionDetails) {
55
58
  return {
@@ -4,8 +4,8 @@
4
4
  * request/response bookkeeping lives in `capture-engine.ts`, shared with the
5
5
  * app's remote-Popcorn transport.
6
6
  */
7
- import type CDP from 'chrome-remote-interface';
8
7
  import { type CaptureSession, type StartCaptureOptions } from './capture-engine.ts';
8
+ import type { CdpTabClient } from './client.ts';
9
9
  export { isStaticAsset, isStaticAssetMime, stripPseudoHeaders, } from './capture-engine.ts';
10
10
  export type { CaptureSession, StartCaptureOptions } from './capture-engine.ts';
11
- export declare function startCapture(tab: CDP.Client, opts?: StartCaptureOptions): Promise<CaptureSession>;
11
+ export declare function startCapture(tab: CdpTabClient, opts?: StartCaptureOptions): Promise<CaptureSession>;
@@ -1,35 +1,39 @@
1
+ /**
2
+ * Local-CDP transport for the capture engine. Binds the page-level `Network.*`
3
+ * events directly and fetches bodies via the page's `getResponseBody`. All the
4
+ * request/response bookkeeping lives in `capture-engine.ts`, shared with the
5
+ * app's remote-Popcorn transport.
6
+ */
1
7
  import { createCaptureEngine, } from "./capture-engine.js";
2
8
  export { isStaticAsset, isStaticAssetMime, stripPseudoHeaders, } from "./capture-engine.js";
3
9
  export async function startCapture(tab, opts = {}) {
4
10
  const engine = createCaptureEngine({
5
11
  ...(opts.maxBodyBytes !== undefined && { maxBodyBytes: opts.maxBodyBytes }),
6
- fetchBody: (requestId) => tab.Network.getResponseBody({ requestId }),
7
- });
8
- const { Network } = tab;
9
- await Network.enable();
10
- Network.requestWillBeSent(engine.onRequestWillBeSent);
11
- // CDP intentionally omits `Cookie` (and other browser-injected headers)
12
- // from `requestWillBeSent.request.headers` for privacy. The real
13
- // outgoing headers — including Cookie — arrive on the matching
14
- // `requestWillBeSentExtraInfo` event.
15
- Network.requestWillBeSentExtraInfo(({ requestId, headers }) => {
16
- engine.mergeRequestHeaders(requestId, headers);
17
- });
18
- Network.responseReceived(engine.onResponseReceived);
19
- // Same privacy carve-out applies to `Set-Cookie` and other browser-
20
- // visible response headers; they arrive on responseReceivedExtraInfo.
21
- Network.responseReceivedExtraInfo(({ requestId, headers }) => {
22
- engine.mergeResponseHeaders(requestId, headers);
23
- });
24
- Network.loadingFinished(({ requestId }) => {
25
- void engine.onLoadingFinished(requestId);
12
+ fetchBody: tab.getResponseBody,
26
13
  });
14
+ const unsubscribe = [
15
+ tab.subscribe('Network.requestWillBeSent', engine.onRequestWillBeSent),
16
+ tab.subscribe('Network.requestWillBeSentExtraInfo', (event) => {
17
+ engine.mergeRequestHeaders(event.requestId, event.headers);
18
+ }),
19
+ tab.subscribe('Network.responseReceived', engine.onResponseReceived),
20
+ tab.subscribe('Network.responseReceivedExtraInfo', (event) => {
21
+ engine.mergeResponseHeaders(event.requestId, event.headers);
22
+ }),
23
+ tab.subscribe('Network.loadingFinished', (event) => {
24
+ void engine.onLoadingFinished(event.requestId);
25
+ }),
26
+ ];
27
+ await tab.send('Network.enable', {});
27
28
  return {
28
29
  captureId: engine.captureId,
29
30
  requests: engine.requests,
30
31
  bodies: engine.bodies,
31
32
  stop: async () => {
32
- await Network.disable();
33
+ for (const stopListening of unsubscribe) {
34
+ stopListening();
35
+ }
36
+ await tab.send('Network.disable', {}).catch(() => { });
33
37
  },
34
38
  };
35
39
  }
@@ -1,9 +1,47 @@
1
1
  import CDP from 'chrome-remote-interface';
2
+ import type { RequestWillBeSentLike, ResponseReceivedLike } from './capture-engine.ts';
3
+ import type { TabInfo } from './tabs.ts';
4
+ interface CdpTabEventMap {
5
+ 'Network.requestWillBeSent': RequestWillBeSentLike;
6
+ 'Network.requestWillBeSentExtraInfo': {
7
+ requestId: string;
8
+ headers: Record<string, string>;
9
+ };
10
+ 'Network.responseReceived': ResponseReceivedLike & {
11
+ response: ResponseReceivedLike['response'] & {
12
+ url: string;
13
+ };
14
+ };
15
+ 'Network.responseReceivedExtraInfo': {
16
+ requestId: string;
17
+ headers: Record<string, string>;
18
+ };
19
+ 'Network.loadingFinished': {
20
+ requestId: string;
21
+ };
22
+ }
23
+ export interface CdpTabClient {
24
+ send: (method: string, params?: object) => Promise<unknown>;
25
+ getResponseBody: (requestId: string) => Promise<{
26
+ body: string;
27
+ base64Encoded: boolean;
28
+ }>;
29
+ subscribe: <K extends keyof CdpTabEventMap>(method: K, listener: (params: CdpTabEventMap[K]) => void) => () => void;
30
+ subscribeOnce: (method: string, listener: () => void) => () => void;
31
+ close: () => Promise<void>;
32
+ }
2
33
  export interface CdpHandles {
3
34
  /** Browser-level connection (target list, attach). */
4
35
  browser: CDP.Client;
5
36
  /** Returns a per-target session bound to a tab. */
6
- attachToTab: (targetId: string) => Promise<CDP.Client>;
37
+ attachToTab: (targetId: string) => Promise<CdpTabClient>;
38
+ /** Enumerate page targets without assuming a localhost discovery endpoint. */
39
+ listTabs: () => Promise<TabInfo[]>;
7
40
  close: () => Promise<void>;
8
41
  }
9
42
  export declare function connectCdp(port: number): Promise<CdpHandles>;
43
+ /** Connect to a browser-level or page-level CDP websocket. Browser-level
44
+ * endpoints (including Popcorn) use flattened Target sessions; a page-level
45
+ * custom endpoint is exposed as one synthetic tab and used directly. */
46
+ export declare function connectCdpUrl(webSocketUrl: string): Promise<CdpHandles>;
47
+ export {};
package/lib/cdp/client.js CHANGED
@@ -1,11 +1,135 @@
1
1
  import CDP from 'chrome-remote-interface';
2
+ import assert from 'node:assert';
3
+ import { EventEmitter } from 'node:events';
4
+ import { listTabs } from "./tabs.js";
5
+ const DIRECT_TARGET_ID = '__direct_cdp_target__';
2
6
  export async function connectCdp(port) {
3
7
  const browser = await CDP({ port });
8
+ const tabs = new Set();
4
9
  return {
5
10
  browser,
6
- attachToTab: (targetId) => CDP({ port, target: targetId }),
11
+ attachToTab: async (targetId) => {
12
+ const tab = adaptCdpClient(await CDP({ port, target: targetId }));
13
+ tabs.add(tab);
14
+ return tab;
15
+ },
16
+ listTabs: () => listTabs(port),
17
+ close: async () => {
18
+ for (const tab of tabs) {
19
+ await tab.close().catch(() => { });
20
+ }
21
+ await browser.close();
22
+ },
23
+ };
24
+ }
25
+ /** Connect to a browser-level or page-level CDP websocket. Browser-level
26
+ * endpoints (including Popcorn) use flattened Target sessions; a page-level
27
+ * custom endpoint is exposed as one synthetic tab and used directly. */
28
+ export async function connectCdpUrl(webSocketUrl) {
29
+ const browser = await CDP({ target: webSocketUrl, local: true });
30
+ let browserLevel = true;
31
+ try {
32
+ await browser.send('Target.getTargets');
33
+ }
34
+ catch {
35
+ browserLevel = false;
36
+ }
37
+ const tabs = new Set();
38
+ const directTab = browserLevel ? undefined : adaptCdpClient(browser);
39
+ return {
40
+ browser,
41
+ listTabs: async () => {
42
+ if (!browserLevel) {
43
+ return [{
44
+ tabId: DIRECT_TARGET_ID, url: '', title: 'Direct CDP target',
45
+ }];
46
+ }
47
+ const { targetInfos } = await browser.send('Target.getTargets');
48
+ return targetInfos
49
+ .filter((target) => target.type === 'page')
50
+ .map((target) => ({
51
+ tabId: target.targetId,
52
+ url: target.url,
53
+ title: target.title,
54
+ }));
55
+ },
56
+ attachToTab: async (targetId) => {
57
+ if (!browserLevel) {
58
+ if (targetId !== DIRECT_TARGET_ID) {
59
+ throw new Error(`Unknown direct CDP target ${targetId}`);
60
+ }
61
+ assert(directTab);
62
+ return directTab;
63
+ }
64
+ const { sessionId } = await browser.send('Target.attachToTarget', {
65
+ targetId,
66
+ flatten: true,
67
+ });
68
+ const tab = flattenedSessionClient(browser, sessionId);
69
+ tabs.add(tab);
70
+ return tab;
71
+ },
7
72
  close: async () => {
73
+ for (const tab of tabs) {
74
+ await tab.close().catch(() => { });
75
+ }
8
76
  await browser.close();
9
77
  },
10
78
  };
11
79
  }
80
+ /** Adapt a flattened browser Target session to the small tab-client surface
81
+ * consumed by capture and authoring. Events are filtered by session id so
82
+ * concurrent tabs cannot observe each other's traffic. */
83
+ function flattenedSessionClient(browser, sessionId) {
84
+ const events = new EventEmitter();
85
+ // The declaration only accepts literal protocol method names; this adapter
86
+ // deliberately forwards the shared authoring layer's dynamic method string.
87
+ const sender = browser.send.bind(browser);
88
+ const onEvent = (message) => {
89
+ if (message.sessionId !== sessionId) {
90
+ return;
91
+ }
92
+ events.emit('event', message);
93
+ events.emit(message.method, message.params);
94
+ };
95
+ browser.on('event', onEvent);
96
+ return tabClient(events, (method, params) => {
97
+ return sender(method, params ?? {}, sessionId);
98
+ }, (requestId) => {
99
+ return sender('Network.getResponseBody', { requestId }, sessionId);
100
+ }, async () => {
101
+ EventEmitter.prototype.removeListener.call(browser, 'event', onEvent);
102
+ await browser.send('Target.detachFromTarget', { sessionId }).catch(() => { });
103
+ });
104
+ }
105
+ function adaptCdpClient(client) {
106
+ const events = new EventEmitter();
107
+ const onEvent = (message) => {
108
+ events.emit(message.method, message.params);
109
+ };
110
+ client.on('event', onEvent);
111
+ // CRI intentionally types send against protocol literals. This adapter is
112
+ // the single boundary where shared authoring commands arrive dynamically.
113
+ const send = client.send.bind(client);
114
+ return tabClient(events, send, (requestId) => {
115
+ return client.send('Network.getResponseBody', { requestId });
116
+ }, async () => {
117
+ EventEmitter.prototype.removeListener.call(client, 'event', onEvent);
118
+ await client.close();
119
+ });
120
+ }
121
+ function tabClient(events, send, getResponseBody, close) {
122
+ return {
123
+ send,
124
+ getResponseBody,
125
+ subscribe(method, listener) {
126
+ events.on(method, listener);
127
+ return () => events.removeListener(method, listener);
128
+ },
129
+ subscribeOnce(method, listener) {
130
+ events.once(method, listener);
131
+ return () => events.removeListener(method, listener);
132
+ },
133
+ close,
134
+ };
135
+ }
package/lib/consts.d.ts CHANGED
@@ -44,6 +44,13 @@ export declare function useOldDevtools(): boolean;
44
44
  * `ConfigStore` (which would create an import cycle).
45
45
  */
46
46
  export declare function resolveOldMode(persisted?: 'old' | 'builder'): boolean;
47
+ /**
48
+ * Where the effective mode came from — the same precedence as
49
+ * `resolveOldMode`: explicit `USE_OLD_DEVTOOLS` env var → persisted config
50
+ * `mode` → default (old). Surfaced by the `get_devtools_mode` tool so the
51
+ * user can tell why the server is in the mode it reports.
52
+ */
53
+ export declare function oldModeSource(persisted?: 'old' | 'builder'): 'env' | 'config' | 'default';
47
54
  /** `%LOCALAPPDATA%` on Windows, used to probe a per-user Chrome install. */
48
55
  export declare function localAppData(): string | undefined;
49
56
  /** Explicit Chrome/Edge/Chromium binary path override. */
package/lib/consts.js CHANGED
@@ -76,6 +76,21 @@ export function resolveOldMode(persisted) {
76
76
  }
77
77
  return true;
78
78
  }
79
+ /**
80
+ * Where the effective mode came from — the same precedence as
81
+ * `resolveOldMode`: explicit `USE_OLD_DEVTOOLS` env var → persisted config
82
+ * `mode` → default (old). Surfaced by the `get_devtools_mode` tool so the
83
+ * user can tell why the server is in the mode it reports.
84
+ */
85
+ export function oldModeSource(persisted) {
86
+ if (oldDevtoolsEnvSet()) {
87
+ return 'env';
88
+ }
89
+ if (persisted) {
90
+ return 'config';
91
+ }
92
+ return 'default';
93
+ }
79
94
  // ---------------------------------------------------------------------------
80
95
  // Chrome / CDP launcher.
81
96
  // ---------------------------------------------------------------------------
@@ -161,7 +161,7 @@ export const MCP_TOOLS = {
161
161
  }),
162
162
  },
163
163
  "AdminSessions": {
164
- description: "Explore verification sessions (admin-only)\n\nAdmin only. Search verification sessions across all organizations — paste a session id, provider id, or consumer ETH address into `q`, optionally filter by `status`. Rows link to the org-scoped session detail page (admins can open any org's pages). Capped at 100, newest first.",
164
+ description: "Explore verification sessions (admin-only)\n\nAdmin only. Search verification sessions across all organizations — filter by `sessionId` (prefix match, at least 5 characters), `providerId`, `claimantClientId`, `orgId` (all exact match), `status`, and a `createdAfter`/`createdBefore` time range. Rows link to the org-scoped session detail page (admins can open any org's pages). Capped at 100, newest first.",
165
165
  inputSchema: toZod({
166
166
  "type": "object",
167
167
  "properties": {
@@ -172,7 +172,19 @@ export const MCP_TOOLS = {
172
172
  "query": {
173
173
  "type": "object",
174
174
  "properties": {
175
- "q": {
175
+ "sessionId": {
176
+ "type": "string",
177
+ "maxLength": 100
178
+ },
179
+ "providerId": {
180
+ "type": "string",
181
+ "maxLength": 100
182
+ },
183
+ "claimantClientId": {
184
+ "type": "string",
185
+ "maxLength": 100
186
+ },
187
+ "orgId": {
176
188
  "type": "string",
177
189
  "maxLength": 100
178
190
  },
@@ -186,6 +198,14 @@ export const MCP_TOOLS = {
186
198
  "error",
187
199
  "expired"
188
200
  ]
201
+ },
202
+ "createdAfter": {
203
+ "type": "string",
204
+ "format": "date-time"
205
+ },
206
+ "createdBefore": {
207
+ "type": "string",
208
+ "format": "date-time"
189
209
  }
190
210
  }
191
211
  },
@@ -589,9 +609,9 @@ export const MCP_TOOLS = {
589
609
  },
590
610
  "prompt": {
591
611
  "type": "string",
592
- "description": "Extraction prompt, used (and required) when `verificationType` is `llm`.",
612
+ "description": "Extraction prompt, used (and required) when `verificationType` is `llm`. Gated by the prompt guard before it is stored (docs/prompt-guard.md).",
593
613
  "minLength": 0,
594
- "maxLength": 1000000
614
+ "maxLength": 4000
595
615
  },
596
616
  "version": {
597
617
  "type": "object",
@@ -1968,7 +1988,7 @@ export const MCP_TOOLS = {
1968
1988
  }),
1969
1989
  },
1970
1990
  "ListOrgVerificationSessions": {
1971
- description: "List an organization's verification sessions\n\nReturns verification sessions created under the organization, newest first. Optionally filter by `providerId`, `status`, `resolvedVersion`, `claimantClientId`, and a `createdAfter`/`createdBefore` time range. Caller must be an active member of the org. Opaque cursor pagination.\n\nNote: `status` filters on the stored column. A session that has passed `expiresAt` but has not yet been swept is reported as `expired` in its body while still stored as `pending`, so `status=expired` may not match such sessions until the sweep runs.",
1991
+ description: "List an organization's verification sessions\n\nReturns verification sessions created under the organization, newest first. Optionally filter by `sessionId` (prefix match, at least 5 characters), `providerId`, `status`, `resolvedVersion`, `claimantClientId`, and a `createdAfter`/`createdBefore` time range. Caller must be an active member of the org. Opaque cursor pagination.\n\nNote: `status` filters on the stored column. A session that has passed `expiresAt` but has not yet been swept is reported as `expired` in its body while still stored as `pending`, so `status=expired` may not match such sessions until the sweep runs.",
1972
1992
  inputSchema: toZod({
1973
1993
  "type": "object",
1974
1994
  "properties": {
@@ -1988,9 +2008,13 @@ export const MCP_TOOLS = {
1988
2008
  "query": {
1989
2009
  "type": "object",
1990
2010
  "properties": {
2011
+ "sessionId": {
2012
+ "type": "string",
2013
+ "maxLength": 100
2014
+ },
1991
2015
  "providerId": {
1992
2016
  "type": "string",
1993
- "format": "uuid"
2017
+ "maxLength": 100
1994
2018
  },
1995
2019
  "status": {
1996
2020
  "type": "string",
@@ -2009,7 +2033,7 @@ export const MCP_TOOLS = {
2009
2033
  },
2010
2034
  "claimantClientId": {
2011
2035
  "type": "string",
2012
- "format": "uuid"
2036
+ "maxLength": 100
2013
2037
  },
2014
2038
  "createdAfter": {
2015
2039
  "type": "string",
@@ -2849,9 +2873,9 @@ export const MCP_TOOLS = {
2849
2873
  },
2850
2874
  "prompt": {
2851
2875
  "type": "string",
2852
- "description": "Extraction prompt, used when `verificationType` is `llm`.",
2876
+ "description": "Extraction prompt, used when `verificationType` is `llm`. Gated by the prompt guard before it is stored (docs/prompt-guard.md).",
2853
2877
  "minLength": 0,
2854
- "maxLength": 1000000
2878
+ "maxLength": 4000
2855
2879
  },
2856
2880
  "requests": {
2857
2881
  "type": "array",
@@ -6,9 +6,28 @@
6
6
  * `lib/mcp` (published) — both are two levels below the package root.
7
7
  * Old-devtools mode has its own reduced set in `../old/instructions.ts`.
8
8
  */
9
+ import assert from 'node:assert';
9
10
  import { readFileSync } from 'node:fs';
10
11
  import { fileURLToPath } from 'node:url';
11
12
  export const BUILDER_MODE_INSTRUCTIONS = readFileSync(fileURLToPath(new URL('../../skill/builder-mode.md', import.meta.url)), 'utf8').trim();
13
+ /**
14
+ * `SKILL.builder.md` and `SKILL.old.md` share a lot of prose (steps that are
15
+ * identical regardless of backend, "never do" rules, common troubleshooting).
16
+ * That shared text lives once in `skill/SKILL.common.md`, split into named
17
+ * `<!-- section: name -->`-delimited blocks; each mode file splices a block
18
+ * back in at `<!-- common: name -->`. Parsed once at module load.
19
+ */
20
+ const COMMON_SECTIONS = parseCommonSections(readFileSync(fileURLToPath(new URL('../../skill/SKILL.common.md', import.meta.url)), 'utf8'));
21
+ function parseCommonSections(text) {
22
+ const sections = {};
23
+ const re = /<!-- section: (\S+) -->\n([\s\S]*?)(?=\n<!-- section: |$)/g;
24
+ for (const match of text.matchAll(re)) {
25
+ // Trim blank LINES only, not inline indentation — some sections are
26
+ // list-continuation text that's intentionally indented on its first line.
27
+ sections[match[1]] = match[2].replace(/^\n+/, '').replace(/\n+$/, '');
28
+ }
29
+ return sections;
30
+ }
12
31
  /**
13
32
  * The full provider-authoring guide — the deep capture→prove→publish workflow,
14
33
  * decision frameworks, and troubleshooting. Served on demand by the
@@ -19,9 +38,14 @@ export const BUILDER_MODE_INSTRUCTIONS = readFileSync(fileURLToPath(new URL('../
19
38
  * the instructions above; any leading frontmatter is stripped defensively.
20
39
  */
21
40
  function readGuide(file) {
22
- return readFileSync(fileURLToPath(new URL(file, import.meta.url)), 'utf8')
23
- .replace(/^---\n[\s\S]*?\n---\n/, '')
24
- .trim();
41
+ const url = new URL(file, import.meta.url);
42
+ const raw = readFileSync(fileURLToPath(url), 'utf8');
43
+ const stripped = raw.replace(/^---\n[\s\S]*?\n---\n/, '').trim();
44
+ return stripped.replace(/<!-- common: (\S+) -->/g, (whole, name) => {
45
+ const section = COMMON_SECTIONS[name];
46
+ assert(section, `${file}: no SKILL.common.md section named "${name}"`);
47
+ return section;
48
+ });
25
49
  }
26
50
  export const PROVIDER_GUIDE_BUILDER = readGuide('../../skill/SKILL.builder.md');
27
51
  export const PROVIDER_GUIDE_OLD = readGuide('../../skill/SKILL.old.md');
package/lib/mcp/start.js CHANGED
@@ -10,7 +10,7 @@ import { ReclaimSdkClient } from "../old/sdk-client.js";
10
10
  import { buildOldBackendTools } from "../old/tools/build.js";
11
11
  import { NAME, VERSION } from "../pkg.js";
12
12
  import { registerTools } from "./api/tools.js";
13
- import { buildAgentTools, buildCredentialTools, buildLocalCredentialTools, buildModeTools, buildVerificationTools, readSession, } from "./tools/index.js";
13
+ import { buildAgentTools, buildBuilderAuthenticationTools, buildBuilderOrganizationTools, buildCredentialTools, buildLocalCredentialTools, buildModeTools, buildVerificationTools, readSession, } from "./tools/index.js";
14
14
  import { BUILDER_MODE_INSTRUCTIONS } from "./instructions.js";
15
15
  import { registerToolList } from "./server.js";
16
16
  export async function startMcpServer() {
@@ -28,6 +28,13 @@ export async function startMcpServer() {
28
28
  // deployments — set RECLAIM_AGENT_DISABLED=1 to register only the API
29
29
  // surface.
30
30
  const agentToolsEnabled = !agentDisabled();
31
+ const builderClient = new ReclaimClient({
32
+ baseUrl: apiUrl(),
33
+ // Builder access is orthogonal to publishing mode. A legacy-devtools
34
+ // session can authenticate here solely to allocate a remote browser.
35
+ token: apiToken() || readSession()?.token,
36
+ headers: { 'User-Agent': `${NAME}/${VERSION}` },
37
+ });
31
38
  // Backend-mode toggle — available in both modes and regardless of whether
32
39
  // the agent tools are enabled, so a user can always switch either direction.
33
40
  registerToolList(server, buildModeTools(config, oldMode));
@@ -42,21 +49,18 @@ export async function startMcpServer() {
42
49
  identity: resolveInitialIdentity(),
43
50
  });
44
51
  const sdkClient = new ReclaimSdkClient({ baseUrl: sdkApiUrl() });
52
+ registerToolList(server, buildBuilderAuthenticationTools(builderClient));
53
+ registerToolList(server, buildBuilderOrganizationTools(builderClient));
45
54
  registerToolList(server, buildLocalCredentialTools());
46
55
  registerToolList(server, buildOldBackendTools(oldClient, sdkClient));
47
56
  if (agentToolsEnabled) {
48
57
  // No builder client → no builder publish tool (old mode has its own).
49
- registerToolList(server, buildAgentTools());
58
+ registerToolList(server, buildAgentTools(undefined, builderClient));
50
59
  }
51
60
  // NOTE: registerTools() is intentionally NOT called — generated tools off.
52
61
  }
53
62
  else {
54
- const client = new ReclaimClient({
55
- baseUrl: apiUrl(),
56
- // Priority: explicit env var → cached session token → unauthenticated
57
- token: apiToken() || readSession()?.token,
58
- headers: { 'User-Agent': `${NAME}/${VERSION}` },
59
- });
63
+ const client = builderClient;
60
64
  // Tool names must stay disjoint across these three groups: hand-written
61
65
  // tools are higher-level orchestrations (multi-call flows, ReclaimProvider
62
66
  // translation) and must NOT reuse a generated operation's snake_case name.
@@ -1,11 +1,25 @@
1
- import { type CdpHandles } from '../../../cdp/client.ts';
1
+ import type { ReclaimClient } from '@reclaimprotocol/client';
2
+ import { type CdpHandles, connectCdp, connectCdpUrl } from '../../../cdp/client.ts';
2
3
  import type { ConfigStore } from '../../../config/store.ts';
3
4
  import { type RegisteredTool } from '../../server.ts';
5
+ export type BrowserMode = 'dedicated' | 'attach' | 'builder' | 'custom';
4
6
  export interface AttachState {
5
7
  handles: CdpHandles;
6
- port: number;
8
+ mode: BrowserMode;
9
+ port?: number;
10
+ liveViewUrl?: string;
11
+ browserSessionId?: string;
12
+ orgId?: string;
13
+ disposeRemote?: () => Promise<void>;
7
14
  }
8
- export declare function attachTool(state: {
15
+ export interface AttachRef {
9
16
  current?: AttachState;
10
17
  config: ConfigStore;
11
- }): RegisteredTool;
18
+ }
19
+ interface AttachDependencies {
20
+ connectLocal: typeof connectCdp;
21
+ connectRemote: typeof connectCdpUrl;
22
+ }
23
+ export declare function attachTool(state: AttachRef, builderClient?: ReclaimClient, name?: 'attach_browser' | 'attach_chrome', beforeReplace?: () => Promise<void>, dependencies?: AttachDependencies): RegisteredTool;
24
+ export declare function disposeAttached(state: AttachRef): Promise<void>;
25
+ export {};