@reclaimprotocol/agent 0.1.25 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -113,6 +113,8 @@ The MCP server targets one of two backends, selected by `USE_OLD_DEVTOOLS`:
113
113
  - `reclaim_authenticate` — by default opens the dashboard in Chrome via CDP, waits for sign-in, reads the Firebase token from page storage, and closes the tab (or pass a `token` / `ethAddress` to skip the browser).
114
114
  - `create_provider_version_from_capture` — registers a new provider, or adds a version to an existing one when given a `providerId`.
115
115
  - `get_me_providers` — list the providers you own (the only provider-listing tool in this mode).
116
+ - `session_analytics_logs` — the milestone events for a verification session, which show how far it got.
117
+ - `session_logs` — the log entries the in-app SDK emitted during that session, each with its event type, log level, and logger name. Filter by event type, level, or substring, page with `limit` and `offset`, or set `saveTo` to write every matching entry to an NDJSON file. The backend searches only the last 3 days unless you pass `startTime` and `endTime`, and it deletes entries after 30 days.
116
118
  - **Builder (`USE_OLD_DEVTOOLS=false`).** Registers the auto-generated HTTP-API tools plus the authentication/credential tools (`authenticate` device-pairing login, `issue_credentials` / `import_credentials` to set up the local eth proof-owner key, `resolve_owner_key`) and a verification-result helper (`get_verification_result`).
117
119
 
118
120
  Relevant env vars: `RECLAIM_OLD_API_URL` (default `https://devapi.reclaimprotocol.org`), `RECLAIM_OLD_API_TOKEN` / `RECLAIM_OLD_ETH_UID` (headless identity), `RECLAIM_OLD_LOGIN_URL` (default `https://dev.reclaimprotocol.org`); builder mode uses `RECLAIM_API_URL` / `RECLAIM_API_TOKEN`.
@@ -2,13 +2,6 @@ import { type CloudflaredBinary } from './cloudflared.ts';
2
2
  export interface Tunnel {
3
3
  /** The public HTTPS origin Cloudflare assigned. */
4
4
  url: string;
5
- /** Whether the tunnel was carrying traffic before {@link READY_TIMEOUT_MS}
6
- * elapsed. False means it may still be propagating rather than broken — say
7
- * so rather than treating the share as failed. */
8
- ready: boolean;
9
- /** cloudflared's own last words, for when `ready` is false and someone has
10
- * to work out why. Absent when the tunnel came up normally. */
11
- diagnostics?: string;
12
5
  /** Terminate the tunnel. Safe to call more than once. */
13
6
  stop: () => Promise<void>;
14
7
  /** Which cloudflared ran, and whether we had to fetch it. */
@@ -21,6 +14,12 @@ export interface Tunnel {
21
14
  * Rejects with an actionable message when `cloudflared` is not installed,
22
15
  * exits early, or never reports a hostname.
23
16
  */
24
- export declare function startTunnel(port: number, { autoInstall }?: {
17
+ export declare function startTunnel(port: number, { autoInstall, probePath, readyPollMs, readyTimeoutMs, }?: {
25
18
  autoInstall?: boolean;
19
+ /** Public path that must answer before the tunnel is returned. */
20
+ probePath?: string;
21
+ /** Test-only timing override. */
22
+ readyPollMs?: number;
23
+ /** Test-only timing override. */
24
+ readyTimeoutMs?: number;
26
25
  }): Promise<Tunnel>;
@@ -25,56 +25,42 @@ import { resolveCloudflared } from "./cloudflared.js";
25
25
  const URL_TIMEOUT_MS = 30000;
26
26
  /** After the hostname is printed, Cloudflare needs a moment before the edge
27
27
  * routes to it — measured at ~3s. Wait for it so the caller never hands out a
28
- * URL that errors, but don't fail the share if it's slow. */
28
+ * URL that errors. */
29
29
  const READY_TIMEOUT_MS = 20000;
30
30
  const READY_POLL_MS = 1000;
31
+ const STOP_TIMEOUT_MS = 5000;
31
32
  /** `https://<something>.trycloudflare.com`, as printed on cloudflared's log
32
33
  * stream. Anchored to the scheme so a hostname inside prose can't match. */
33
34
  const QUICK_TUNNEL_URL = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
34
- /**
35
- * cloudflared says this once per edge connection it establishes, and until it
36
- * does, the hostname resolves but routes nowhere.
37
- *
38
- * This is the signal to trust, because it is LOCAL. Asking whether we can fetch
39
- * the public URL ourselves conflates two questions — is the tunnel up, and does
40
- * this machine have egress to Cloudflare's edge — and reports "not ready" when
41
- * only the second fails. Observed: a tunnel printed its hostname and registered
42
- * nothing for 12s, which is indistinguishable from a blocked fetch unless you
43
- * read cloudflared's own account of it.
44
- */
45
- const REGISTERED = /Registered tunnel connection|connIndex=/i;
46
35
  /** Tail of cloudflared's log kept for error messages. */
47
36
  const LOG_TAIL_BYTES = 4096;
48
37
  /**
49
38
  * Wait until the tunnel is actually carrying traffic.
50
39
  *
51
- * Two independent signals, either of which is proof:
52
- * - cloudflared reported an edge registration (see {@link REGISTERED}). Local,
53
- * so it works regardless of this machine's own egress.
54
- * - the public origin answered. ANY HTTP response counts, including a 404:
55
- * the live-view server 404s unknown paths, so a status code at all proves
56
- * the hop is live. Kept as a second opinion in case a cloudflared release
57
- * rewords its log.
40
+ * A cloudflared "Registered tunnel connection" log is not enough: Cloudflare
41
+ * can report one while the assigned hostname still returns DNS NXDOMAIN. The
42
+ * public viewer itself must return a successful response.
58
43
  */
59
- async function waitUntilRouted(url, isRegistered) {
60
- const deadline = Date.now() + READY_TIMEOUT_MS;
44
+ async function waitUntilRouted(url, { isRunning, pollMs, timeoutMs, }) {
45
+ const deadline = Date.now() + timeoutMs;
61
46
  while (Date.now() < deadline) {
62
- if (isRegistered()) {
63
- return true;
47
+ if (!isRunning()) {
48
+ return false;
64
49
  }
65
50
  try {
66
- await fetch(url, {
51
+ const response = await fetch(url, {
67
52
  method: 'GET',
68
53
  redirect: 'manual',
69
- signal: AbortSignal.timeout(READY_POLL_MS * 2),
54
+ signal: AbortSignal.timeout(pollMs * 2),
70
55
  });
71
- return true;
72
- }
73
- catch {
74
- await new Promise((r) => setTimeout(r, READY_POLL_MS));
56
+ if (response.ok) {
57
+ return true;
58
+ }
75
59
  }
60
+ catch { }
61
+ await new Promise((r) => setTimeout(r, pollMs));
76
62
  }
77
- return isRegistered();
63
+ return false;
78
64
  }
79
65
  /**
80
66
  * Open a quick tunnel to `http://127.0.0.1:<port>` and resolve once
@@ -83,7 +69,7 @@ async function waitUntilRouted(url, isRegistered) {
83
69
  * Rejects with an actionable message when `cloudflared` is not installed,
84
70
  * exits early, or never reports a hostname.
85
71
  */
86
- export async function startTunnel(port, { autoInstall = true } = {}) {
72
+ export async function startTunnel(port, { autoInstall = true, probePath = '/', readyPollMs = READY_POLL_MS, readyTimeoutMs = READY_TIMEOUT_MS, } = {}) {
87
73
  // Resolves an existing cloudflared, or fetches one on first use. Throws
88
74
  // with an actionable message when neither is possible.
89
75
  const binary = await resolveCloudflared({ autoInstall });
@@ -92,20 +78,12 @@ export async function startTunnel(port, { autoInstall = true } = {}) {
92
78
  '--no-autoupdate',
93
79
  '--url', `http://127.0.0.1:${port}`,
94
80
  ], { stdio: ['ignore', 'pipe', 'pipe'] });
95
- const stop = async () => {
96
- if (!child.killed) {
97
- child.kill('SIGTERM');
98
- }
99
- };
81
+ const exited = new Promise((resolve) => {
82
+ child.once('exit', () => resolve());
83
+ });
84
+ let stopPromise;
85
+ const stop = () => stopPromise ?? (stopPromise = stopChild());
100
86
  return new Promise((resolve, reject) => {
101
- const timer = setTimeout(() => {
102
- void stop();
103
- reject(new Error('cloudflared did not report a tunnel hostname within '
104
- + `${URL_TIMEOUT_MS / 1000}s. Check network egress to Cloudflare, `
105
- + 'or run `cloudflared tunnel --url http://127.0.0.1:'
106
- + `${port}` + '` by hand to see its output.'));
107
- }, URL_TIMEOUT_MS);
108
- let registered = false;
109
87
  let tail = '';
110
88
  let settled = false;
111
89
  const finish = (fn) => {
@@ -116,27 +94,42 @@ export async function startTunnel(port, { autoInstall = true } = {}) {
116
94
  clearTimeout(timer);
117
95
  fn();
118
96
  };
97
+ const timer = setTimeout(() => {
98
+ finish(() => {
99
+ void stop().then(() => reject(new Error('cloudflared did not report a tunnel hostname within '
100
+ + `${URL_TIMEOUT_MS / 1000}s. Check network egress to `
101
+ + 'Cloudflare, or run `cloudflared tunnel --url '
102
+ + `http://127.0.0.1:${port}` + '` by hand to see its output.')));
103
+ });
104
+ }, URL_TIMEOUT_MS);
119
105
  // cloudflared logs the assigned hostname to stderr, not stdout. Watch
120
106
  // both so a future version that switches streams still works.
121
107
  const onChunk = (chunk) => {
122
108
  const text = chunk.toString();
123
- // Keep watching after the hostname: registration lands a beat later, and
124
- // on a bad run it never lands at all, which is the case worth reporting.
109
+ // Keep watching after the hostname so a readiness failure can include
110
+ // cloudflared's account of what happened.
125
111
  tail = (tail + text).slice(-LOG_TAIL_BYTES);
126
- if (REGISTERED.test(text)) {
127
- registered = true;
128
- }
129
112
  const match = QUICK_TUNNEL_URL.exec(text);
130
113
  if (match) {
131
114
  const url = match[0];
132
115
  finish(() => {
133
- void waitUntilRouted(url, () => registered).then((ready) => {
116
+ const probeUrl = new URL(probePath, `${url}/`).toString();
117
+ void waitUntilRouted(probeUrl, {
118
+ isRunning: () => child.exitCode === null
119
+ && child.signalCode === null,
120
+ pollMs: readyPollMs,
121
+ timeoutMs: readyTimeoutMs,
122
+ }).then(async (ready) => {
134
123
  if (ready) {
135
- resolve({ url, ready, stop, binary });
124
+ resolve({ url, stop, binary });
136
125
  return;
137
126
  }
138
- LOGGER.warn({ url, tail }, 'tunnel never registered with an edge; returning anyway');
139
- resolve({ url, ready, stop, binary, diagnostics: tail });
127
+ await stop();
128
+ LOGGER.warn({ url, tail }, 'tunnel public URL never became reachable');
129
+ reject(new Error('cloudflared created a hostname, but the public viewer did '
130
+ + `not become reachable within ${readyTimeoutMs / 1000}s. `
131
+ + 'Retry share_browser_view. cloudflared output:\n'
132
+ + tail));
140
133
  });
141
134
  });
142
135
  }
@@ -157,4 +150,24 @@ export async function startTunnel(port, { autoInstall = true } = {}) {
157
150
  + 'hostname.')));
158
151
  });
159
152
  });
153
+ async function stopChild() {
154
+ if (child.exitCode !== null || child.signalCode !== null) {
155
+ return;
156
+ }
157
+ let timeout;
158
+ const timedOut = new Promise((resolve) => {
159
+ timeout = setTimeout(resolve, STOP_TIMEOUT_MS);
160
+ });
161
+ child.kill('SIGTERM');
162
+ await Promise.race([
163
+ exited,
164
+ timedOut,
165
+ ]);
166
+ clearTimeout(timeout);
167
+ if (child.exitCode !== null || child.signalCode !== null) {
168
+ return;
169
+ }
170
+ child.kill('SIGKILL');
171
+ await exited;
172
+ }
160
173
  }
@@ -111,6 +111,10 @@ export declare const MCP_TOOLS: {
111
111
  description: string;
112
112
  inputSchema: import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
113
113
  };
114
+ ImportDevtoolsProvider: {
115
+ description: string;
116
+ inputSchema: import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
117
+ };
114
118
  IssueOrgToken: {
115
119
  description: string;
116
120
  inputSchema: import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
@@ -1812,6 +1812,46 @@ export const MCP_TOOLS = {
1812
1812
  "additionalProperties": false
1813
1813
  }),
1814
1814
  },
1815
+ "ImportDevtoolsProvider": {
1816
+ description: "Import a provider from devtools\n\nImport the latest version of a public provider from the legacy devtools\nservice. Use this when moving an existing provider to Builder instead\nof recreating it.\n\nThe import preserves the legacy provider UUID. The server reads the\ncurrent configuration from\n`https://api.reclaimprotocol.org/api/providers/{providerId}` and\ntranslates it to the Builder schema.\n\nIf the provider already exists in the seeded anonymous catalog, the\nimport assigns that record to the target organization and adds the\nimported version idempotently. A provider owned by another Builder\norganization returns `409`.\n",
1817
+ inputSchema: toZod({
1818
+ "type": "object",
1819
+ "properties": {
1820
+ "params": {
1821
+ "type": "object",
1822
+ "properties": {}
1823
+ },
1824
+ "query": {
1825
+ "type": "object",
1826
+ "properties": {}
1827
+ },
1828
+ "body": {
1829
+ "type": "object",
1830
+ "additionalProperties": false,
1831
+ "required": [
1832
+ "orgId",
1833
+ "providerId"
1834
+ ],
1835
+ "properties": {
1836
+ "orgId": {
1837
+ "type": "string",
1838
+ "format": "uuid"
1839
+ },
1840
+ "providerId": {
1841
+ "type": "string",
1842
+ "format": "uuid"
1843
+ }
1844
+ }
1845
+ }
1846
+ },
1847
+ "required": [
1848
+ "params",
1849
+ "query",
1850
+ "body"
1851
+ ],
1852
+ "additionalProperties": false
1853
+ }),
1854
+ },
1815
1855
  "IssueOrgToken": {
1816
1856
  description: "Issue a new organization API key\n\nIssues a fresh per-organization API key (`rorg_…`). The caller must be an active **OWNER** of the organization. An org may hold several keys; issuing a new one does **not** invalidate existing keys (revoke those explicitly). The plaintext key is returned **only once** in this response (the server stores only its HMAC); treat it like a password.",
1817
1857
  inputSchema: toZod({
@@ -61,6 +61,8 @@ const SHARED_TOPIC_FILES = {
61
61
  const TOPIC_SUMMARIES = {
62
62
  'overview': 'Terminology, your stance, and the whole loop at a glance.',
63
63
  'auth': 'Signing in to the backend, and which token authorizes what.',
64
+ 'devtools-migration': 'Importing a public legacy provider with the same '
65
+ + 'UUID.',
64
66
  'browser': 'Choosing a browser source (local Chrome, the Reclaim runtime in '
65
67
  + 'Docker, a remote Popcorn browser, or a custom CDP endpoint), '
66
68
  + 'geolocation, live view, and launch troubleshooting.',
@@ -83,7 +83,9 @@ export function liveViewTools(attachRef, shareRef) {
83
83
  const viewerPort = attached.viewerPort;
84
84
  assert(viewerPort, badRequest('This browser reports no live-view port. Re-attach with mode '
85
85
  + '"container".'));
86
- const tunnel = await startTunnel(viewerPort);
86
+ const tunnel = await startTunnel(viewerPort, {
87
+ probePath: '/liveview.html?magnify=1',
88
+ });
87
89
  // The path is the runtime's own viewer page; `magnify=1` is what makes
88
90
  // it lay out for the device that opens it.
89
91
  const url = `${tunnel.url}/liveview.html?magnify=1`;
@@ -97,30 +99,11 @@ export function liveViewTools(attachRef, shareRef) {
97
99
  ...(share.tunnel.binary.source === 'downloaded'
98
100
  ? { cloudflaredInstalled: true }
99
101
  : {}),
100
- ...(share.tunnel.ready
101
- ? {}
102
- : {
103
- propagating: true,
104
- // cloudflared's own last words. Without them, "the URL does
105
- // not connect" is unactionable: the tunnel, the local server
106
- // and this machine's egress all look identical from outside.
107
- ...(share.tunnel.diagnostics
108
- ? { cloudflaredLog: share.tunnel.diagnostics }
109
- : {}),
110
- }),
111
102
  _notes: [
112
103
  ...(share.tunnel.binary.source === 'downloaded'
113
104
  ? ['cloudflared was not installed, so it was downloaded once '
114
105
  + 'to ~/.reclaim/bin. Mention that; later shares reuse it.']
115
106
  : []),
116
- ...(share.tunnel.ready
117
- ? []
118
- : ['The tunnel never confirmed a connection to Cloudflare, so '
119
- + 'the URL may not resolve yet. Give it ~10s and reload '
120
- + 'first. If it still fails, say so and offer to retry '
121
- + '(stop_browser_view then share_browser_view) — a quick '
122
- + 'tunnel does sometimes fail to register. '
123
- + '`cloudflaredLog` has its output if it keeps happening.']),
124
107
  'Relay `url` to the developer verbatim, on its own line.',
125
108
  'The URL is a credential: it needs no login, and anyone with it '
126
109
  + 'can see this browser and act in it as the signed-in user.',
@@ -94,6 +94,70 @@ export interface LinkNonce {
94
94
  nonce: string;
95
95
  expiresInSeconds: number;
96
96
  }
97
+ /** Log-level filter accepted by the devtools backend's session-logs route.
98
+ * It validates against this exact list and rejects anything else with a 400,
99
+ * including the comma-separated lists the log-stream service itself would
100
+ * accept. Each value is hierarchical at the log-stream end: `fine` means
101
+ * FINE+CONFIG+INFO+WARNING+SEVERE, `info` means INFO+WARNING+SEVERE, and so
102
+ * on. `finer`/`finest` (the PII tiers) are not reachable through this route
103
+ * at all. */
104
+ export type SessionLogLevel = 'fine' | 'config' | 'info' | 'warning' | 'severe' | 'unknown';
105
+ /** Filters for {@link ReclaimOldClient.getSessionLogs}. Every one is optional
106
+ * and forwarded verbatim; the session id itself is the route's path param, so
107
+ * `providerId`/`deviceId` narrow WITHIN one session rather than searching
108
+ * across sessions. */
109
+ export interface SessionLogsQuery {
110
+ deviceId?: string;
111
+ source?: string;
112
+ /** The client-side logger NAME (log-stream's `log_type` column, the SDK's
113
+ * `LogEntry.type`) — not the event type. */
114
+ logType?: string;
115
+ logLevel?: SessionLogLevel;
116
+ providerId?: string;
117
+ /** ISO-8601. Omitting BOTH bounds makes the log-stream service default to
118
+ * the last 3 days. */
119
+ startTime?: string;
120
+ endTime?: string;
121
+ /** Substring match against the log line (`LIKE %…%`). */
122
+ logLine?: string;
123
+ /** Exact `LogEventType` name, for example `REQUEST_MATCHED`. */
124
+ eventType?: string;
125
+ /** Capped at 1000 by the log-stream service. */
126
+ limit?: number;
127
+ offset?: number;
128
+ includeCount?: boolean;
129
+ }
130
+ /** One raw log line, in the log-stream service's snake_case ClickHouse shape,
131
+ * passed through unchanged by the devtools backend. `event_type` is the SDK's
132
+ * `LogEventType` milestone marker (`REQUEST_MATCHED`,
133
+ * `CLAIM_CREATION_STARTED`, `PROOF_GENERATED`, …); it is `''` on the majority
134
+ * of lines, which carry no event. */
135
+ export interface SessionLogRow {
136
+ timestamp: string;
137
+ request_id: string;
138
+ session_id: string;
139
+ device_id: string;
140
+ source: string;
141
+ app_id: string;
142
+ provider_id: string;
143
+ log_type: string;
144
+ log_level: string;
145
+ log_line: string;
146
+ event_type?: string;
147
+ metadata?: string;
148
+ }
149
+ /** `GET /api/logger/:sessionId`'s `data` envelope. The backend queries every
150
+ * region that holds the session, concatenates the pages, sorts them
151
+ * newest-first and re-slices to `limit`, so `data` is one page and
152
+ * `totalCount` is the match count across all of them. */
153
+ export interface SessionLogsPage {
154
+ data: SessionLogRow[];
155
+ count: number;
156
+ totalCount?: number;
157
+ /** Regions whose log-stream query failed — the page is INCOMPLETE when
158
+ * this is present. */
159
+ failedRegions?: string[];
160
+ }
97
161
  /**
98
162
  * Minimal REST client for the OLD devtools Express backend (base `/api`).
99
163
  * Deliberately NOT a clone of `@reclaimprotocol/client`'s operationId
@@ -133,6 +197,25 @@ export declare class ReclaimOldClient {
133
197
  * `#request`). Attaches the caller identity when one is set; queried
134
198
  * anonymously otherwise. */
135
199
  getSessionAnalyticsLogs(sessionId: string): Promise<unknown>;
200
+ /**
201
+ * Raw log LINES for a verification session — the SDK's own diagnostic
202
+ * logging, the thing the dashboard's session "logs" tab renders, and a
203
+ * strictly richer signal than `getSessionAnalyticsLogs`'s milestone events.
204
+ *
205
+ * This is the devtools backend (`GET /api/logger/:sessionId`), NOT the
206
+ * analytics-logs host — hence `#request`, and hence the same identity and
207
+ * ownership rules as every other authed call here: the session's app must
208
+ * be one you created (admins excepted), or the route 404s.
209
+ *
210
+ * The backend fans the query out to the per-region log-stream services,
211
+ * which read the ClickHouse `logs` table. Two defaults applied down there
212
+ * bite hard if you don't override them:
213
+ * - no `startTime`/`endTime` → only the last 3 days are searched;
214
+ * - no `logLevel` → INFO and above only, so every FINE/CONFIG line is
215
+ * invisible.
216
+ * Entries are dropped entirely after 30 days (table TTL).
217
+ */
218
+ getSessionLogs(sessionId: string, query?: SessionLogsQuery): Promise<SessionLogsPage>;
136
219
  /** Mint a brand-new, unassociated (sandboxed) app — no auth required. Link
137
220
  * it to an account afterwards with `getLinkNonce` + `linkAppToAccount`, or
138
221
  * use it as-is within the sandbox session limit. */
package/lib/old/client.js CHANGED
@@ -89,6 +89,52 @@ export class ReclaimOldClient {
89
89
  }
90
90
  return json;
91
91
  }
92
+ /**
93
+ * Raw log LINES for a verification session — the SDK's own diagnostic
94
+ * logging, the thing the dashboard's session "logs" tab renders, and a
95
+ * strictly richer signal than `getSessionAnalyticsLogs`'s milestone events.
96
+ *
97
+ * This is the devtools backend (`GET /api/logger/:sessionId`), NOT the
98
+ * analytics-logs host — hence `#request`, and hence the same identity and
99
+ * ownership rules as every other authed call here: the session's app must
100
+ * be one you created (admins excepted), or the route 404s.
101
+ *
102
+ * The backend fans the query out to the per-region log-stream services,
103
+ * which read the ClickHouse `logs` table. Two defaults applied down there
104
+ * bite hard if you don't override them:
105
+ * - no `startTime`/`endTime` → only the last 3 days are searched;
106
+ * - no `logLevel` → INFO and above only, so every FINE/CONFIG line is
107
+ * invisible.
108
+ * Entries are dropped entirely after 30 days (table TTL).
109
+ */
110
+ async getSessionLogs(sessionId, query = {}) {
111
+ const res = await __classPrivateFieldGet(this, _ReclaimOldClient_instances, "m", _ReclaimOldClient_request).call(this, 'GET', `/logger/${encodeURIComponent(sessionId)}`, {
112
+ auth: true,
113
+ query: {
114
+ deviceId: query.deviceId,
115
+ source: query.source,
116
+ logType: query.logType,
117
+ logLevel: query.logLevel,
118
+ providerId: query.providerId,
119
+ startTime: query.startTime,
120
+ endTime: query.endTime,
121
+ logLine: query.logLine,
122
+ eventType: query.eventType,
123
+ limit: query.limit,
124
+ offset: query.offset,
125
+ includeCount: query.includeCount === undefined
126
+ ? undefined
127
+ : String(query.includeCount),
128
+ },
129
+ });
130
+ const page = res.data;
131
+ return {
132
+ data: page?.data ?? [],
133
+ count: page?.count ?? page?.data?.length ?? 0,
134
+ totalCount: page?.totalCount,
135
+ failedRegions: page?.failedRegions,
136
+ };
137
+ }
92
138
  /** Mint a brand-new, unassociated (sandboxed) app — no auth required. Link
93
139
  * it to an account afterwards with `getLinkNonce` + `linkAppToAccount`, or
94
140
  * use it as-is within the sandbox session limit. */
@@ -8,6 +8,7 @@ import { getMeProvidersTool } from "./me-providers.js";
8
8
  import { createVersionFromCaptureTool } from "./publish.js";
9
9
  import { scaffoldDemoServerTool } from "./scaffold-demo-server.js";
10
10
  import { sessionAnalyticsLogsTool } from "./session-analytics-logs.js";
11
+ import { sessionLogsTool } from "./session-logs.js";
11
12
  import { updateProviderMetadataTool } from "./update-provider-metadata.js";
12
13
  import { verifySdkSetupTool } from "./verify-sdk-setup.js";
13
14
  /**
@@ -32,6 +33,7 @@ export function buildOldBackendTools(client, sdkClient, attachRef) {
32
33
  getProviderInfoTool(sdkClient),
33
34
  updateProviderMetadataTool(client),
34
35
  sessionAnalyticsLogsTool(client),
36
+ sessionLogsTool(client),
35
37
  issueAppCredentialsTool(client),
36
38
  linkAppTool(client),
37
39
  checkAppStatusTool(client),
@@ -0,0 +1,3 @@
1
+ import { type RegisteredTool } from '../../mcp/server.ts';
2
+ import type { ReclaimOldClient } from '../client.ts';
3
+ export declare function sessionLogsTool(client: ReclaimOldClient): RegisteredTool;