creditkarma-mcp 2.0.9 → 2.1.4

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
@@ -21,7 +21,29 @@ Ask Claude things like:
21
21
  - [Claude Desktop](https://claude.ai/download) or [Claude Code](https://claude.ai/code)
22
22
  - [Node.js](https://nodejs.org) 18 or later
23
23
  - A Credit Karma account
24
- - [Google Chrome](https://www.google.com/chrome/) — used once for the scripted auth flow (optional; you can copy the cookie manually instead)
24
+ - For the no-env-var path: the [fetchproxy 0.3.0 Chrome / Safari extension](https://github.com/chrischall/fetchproxy)
25
+
26
+ ## Acknowledgement of Terms
27
+
28
+ By using this MCP server, you acknowledge and agree to the following:
29
+
30
+ **1. This server accesses your own Credit Karma account.** Every request is dispatched through your own signed-in browser tab via the fetchproxy extension. **You** are the one logged in. It does not — and cannot — access anyone else's account.
31
+
32
+ **2. [Credit Karma's Terms](https://www.creditkarma.com/about/terms) govern your use of this server**, just as they govern your direct use of creditkarma.com. The clauses most relevant here:
33
+
34
+ > You must not sell, transfer, or assign your account to anyone else… you may not allow anyone else to log into our Services as you.
35
+
36
+ CK does contemplate third-party data retrieval at the user's direction (Section 3.7). There is no explicit anti-scraping clause in the membership agreement; Section 4.1 restricts copying or distributing CK content without express prior written consent.
37
+
38
+ You are agreeing to those terms — read by the maintainer 2026-05-23 — every time you invoke a tool in this server. Critically: this server runs **as you**, not as a third party logging in on your behalf. You direct the tool.
39
+
40
+ **3. Personal, non-commercial use only.** This project is not affiliated with, endorsed by, sponsored by, or in partnership with Intuit, Credit Karma, or any financial institution. It is a personal automation tool that reads your transaction history, spending categories, and account snapshots — the same data Credit Karma already shows you in their app. Do not use it on someone else's account, do not redistribute their content, and do not use it to make trading or lending decisions on behalf of others.
41
+
42
+ **4. This server may break.** Credit Karma rotates its internal endpoints; what works today may 404 tomorrow. This is the nature of unofficial integrations.
43
+
44
+ **5. You accept full responsibility** for any consequences of using this server in connection with your Credit Karma account — rate limiting, account warnings, suspension, or any enforcement action Intuit takes. If Credit Karma objects to your use, stop using this server. **Do not commit your `.env` to git** — your CK session/auth artifacts are credentials, and the Membership Agreement holds you responsible for their confidentiality.
45
+
46
+ This section is the maintainer's good-faith summary of the terms — it is not legal advice and does not modify or supersede Credit Karma's actual Membership Agreement.
25
47
 
26
48
  ## Installation
27
49
 
@@ -80,38 +102,31 @@ Fully quit and relaunch. Then ask: *"Sync my Credit Karma transactions"*.
80
102
 
81
103
  Credit Karma uses short-lived JWTs. This server handles automatic token refresh — you only need to set up credentials once (or when your session expires).
82
104
 
83
- ### Getting your credentials
105
+ `creditkarma-mcp` tries three auth paths in priority order; whichever succeeds first is used. Existing setups keep working unchanged.
84
106
 
85
- #### Option A scripted (recommended)
107
+ 1. **`CK_COOKIES` env var (legacy).** Set the full Cookie header in your Claude Desktop config or `.env`. This is the path shown in the config above.
108
+ 2. **Cached session from `ck_set_session`.** Once called, the tool persists the Cookie header to `.env` as `CK_COOKIES` — so subsequent runs collapse into path 1.
109
+ 3. **fetchproxy fallback (no env vars needed — easiest onboarding).** When neither is configured, the server reads `CKAT` + `CKTRKID` cookies once at startup from your already-signed-in `creditkarma.com` tab via the [fetchproxy](https://github.com/chrischall/fetchproxy) browser extension. After that one read, all CK API calls go directly from Node — the extension is **not** in the request hot path. Install the fetchproxy extension (Chrome Web Store / Safari `.dmg`), sign into [creditkarma.com](https://www.creditkarma.com), and the MCP just works.
86
110
 
87
- ```bash
88
- npm run auth # prints the Cookie header to the console
89
- npm run auth -- .env # writes CK_COOKIES=<header> to .env
90
- ```
111
+ Set `CK_DISABLE_FETCHPROXY=1` to opt out of the fallback (turns missing credentials into a hard error — useful in headless CI).
91
112
 
92
- Launches Chrome with a dedicated profile at `~/.creditkarma-mcp/chrome-profile`, waits for you to sign in at creditkarma.com, then captures the full session Cookie header (CKAT carries the access + refresh JWTs; CKTRKID and friends are needed by the refresh endpoint). Either prints it (for pasting into Claude Desktop / MCPB) or writes it to the env file you pass at mode 0600 (owner-only). Requires Google Chrome installed locally; on first run the script installs `puppeteer-core`, `puppeteer-extra`, and `puppeteer-extra-plugin-stealth` (a few MB, not added to `package.json`).
113
+ ### Getting your credentials (env-var path)
93
114
 
94
- #### Option Bmanual paste (secure prompt)
115
+ #### Option Afetchproxy extension (recommended)
95
116
 
96
- ```bash
97
- npm run auth -- --manual # prompts for the cookie, prints CK_COOKIES
98
- npm run auth -- --manual .env # prompts for the cookie, writes to .env
99
- ```
117
+ 1. Install the [fetchproxy 0.3.0 extension](https://github.com/chrischall/fetchproxy) (Chrome Web Store or Safari `.dmg`).
118
+ 2. Sign into [creditkarma.com](https://www.creditkarma.com) in that browser.
119
+ 3. Leave `CK_COOKIES` **unset** in your Claude config.
100
120
 
101
- Use this if the scripted flow hits Intuit/Akamai bot detection (sign-in returns "A technical issue has unexpectedly occurred"). Grab the Cookie header from your normal Chrome (Option C below), then paste it at the prompt. Input is **not echoed** — paste, press Enter.
121
+ The MCP reads the HttpOnly `CKAT` + `CKTRKID` cookies via `chrome.cookies.get` on the first tool call, then operates direct-to-API from Node. To re-auth (e.g. after Credit Karma signs you out), just sign back in to creditkarma.com.
102
122
 
103
- #### Option C — manual (DevTools)
123
+ #### Option B — manual (DevTools)
104
124
 
105
125
  1. Log in to [creditkarma.com](https://www.creditkarma.com) in Chrome
106
126
  2. Open DevTools → **Network** → click any request to creditkarma.com → **Request Headers**
107
127
  3. Right-click the `cookie` header → **Copy value**
108
128
 
109
- ### Setting credentials
110
-
111
- Either of these works:
112
-
113
- - Paste the value from `npm run auth` into `CK_COOKIES` in your `.env` or Claude config
114
- - Or call `ck_set_session` from within Claude with the Cookie header value
129
+ Then either paste into `CK_COOKIES` in your Claude config / `.env`, or call `ck_set_session` from within Claude with the Cookie header value.
115
130
 
116
131
  The server extracts the access and refresh JWTs from the `CKAT` cookie inside the header and refreshes the access token automatically as needed.
117
132
 
@@ -119,7 +134,9 @@ The server extracts the access and refresh JWTs from the `CKAT` cookie inside th
119
134
 
120
135
  - **Access token**: ~15 minutes (auto-refreshed transparently)
121
136
  - **Refresh token**: ~8 hours
122
- - When the refresh token expires, re-run `npm run auth` (or grab a fresh Cookie header from DevTools) and either update `CK_COOKIES` or call `ck_set_session`
137
+ - When the refresh token expires:
138
+ - **fetchproxy path:** sign back into creditkarma.com — the MCP re-reads fresh cookies on the next tool call.
139
+ - **env-var path:** grab a fresh Cookie header from DevTools and update `CK_COOKIES` (or call `ck_set_session`).
123
140
 
124
141
  ## Available tools
125
142
 
@@ -156,14 +173,19 @@ sync_state (key, value)
156
173
 
157
174
  | Env var | Description | Default |
158
175
  |---------|-------------|---------|
159
- | `CK_COOKIES` | Full Cookie header from a signed-in creditkarma.com request | *(required)* |
176
+ | `CK_COOKIES` | Full Cookie header from a signed-in creditkarma.com request | *(unset — falls back to fetchproxy)* |
177
+ | `CK_DISABLE_FETCHPROXY` | Set to `1` to skip the fetchproxy fallback (headless / CI) | *(unset)* |
160
178
  | `CK_DB_PATH` | Path to SQLite database file | `~/.creditkarma-mcp/transactions.db` |
161
179
 
162
180
  ## Troubleshooting
163
181
 
164
- **"TOKEN_EXPIRED"** — your refresh token has expired. Re-run `npm run auth` (or grab a fresh Cookie header) and update `CK_COOKIES` or call `ck_set_session`.
182
+ **"CK auth: set CK_COOKIES, or call the ck_set_session MCP tool, or install the fetchproxy extension…"** — neither auth path is configured. Either fill in `CK_COOKIES` in your Claude config, or install the [fetchproxy extension](https://github.com/chrischall/fetchproxy) and sign into `creditkarma.com` in your browser.
183
+
184
+ **"TOKEN_EXPIRED"** — your refresh token has expired. Sign back into creditkarma.com (fetchproxy path) or grab a fresh Cookie header from DevTools and update `CK_COOKIES` / call `ck_set_session`.
185
+
186
+ **"fetchproxy fallback failed"** — the env-var path wasn't configured and the extension couldn't be reached. Confirm the fetchproxy extension is installed, signed into Credit Karma, and that it's running (open the extension popup). To disable the fallback, set `CK_DISABLE_FETCHPROXY=1`.
165
187
 
166
- **Sync returns 0 transactions** — check that your `CK_COOKIES` value is fresh. The refresh token inside the CKAT cookie expires after ~8 hours.
188
+ **Sync returns 0 transactions** — check that your auth is fresh. The refresh token inside the CKAT cookie expires after ~8 hours.
167
189
 
168
190
  **Tools not appearing** — fully quit and relaunch Claude Desktop. In Claude Code, run `/mcp` to check server status.
169
191
 
@@ -171,9 +193,10 @@ sync_state (key, value)
171
193
 
172
194
  ## Security
173
195
 
174
- - Credentials are stored only in your local `.env` file (gitignored) or Claude config
175
- - `.env` is written at mode 0600 (owner read/write only) by both `npm run auth` and `ck_set_session`
196
+ - Credentials are stored only in your local `.env` file (gitignored), Claude config, or your browser's cookie jar (fetchproxy path)
197
+ - `.env` is written at mode 0600 (owner read/write only) by `ck_set_session`
176
198
  - `ck_set_session` refuses to save a refresh token whose JWT `exp` is already in the past — prevents stale credentials from polluting `.env`
199
+ - The fetchproxy path doesn't persist anything to disk — cookies are read into memory once per MCP run, directly from the user's browser via `chrome.cookies.get`
177
200
  - The server never logs credentials; warnings go to stderr only (stdout is reserved for the MCP JSON-RPC stream)
178
201
  - Only `SELECT` queries are permitted via `ck_query_sql` — no writes to Credit Karma; the underlying `node:sqlite` `prepare()` also rejects multi-statement input
179
202
 
@@ -196,6 +219,7 @@ Changes land via PR, including for solo work — release notes are generated fro
196
219
 
197
220
  ```
198
221
  src/
222
+ auth.ts resolveAuth() — three-path priority (CK_COOKIES env / ck_set_session cache / fetchproxy), plus loadAuthIntoClient()
199
223
  client.ts Credit Karma GraphQL client (auto-refresh, JWT helpers, cookie parser)
200
224
  index.ts MCP server entry point; bootstraps tokens from CK_COOKIES
201
225
  db.ts SQLite schema, migrations, and upsert helpers
@@ -207,13 +231,11 @@ src/
207
231
  ck_get_spending_by_category, ck_get_spending_by_merchant,
208
232
  ck_get_account_summary
209
233
  sql.ts ck_query_sql — SELECT-only escape hatch
210
- scripts/
211
- setup-auth.mjs npm run auth — Puppeteer flow + manual paste fallback
212
234
  tests/
213
235
  helpers.ts Shared test helpers (fakeServer, makeJwt)
236
+ auth.test.ts resolveAuth + loadAuthIntoClient (mocks @fetchproxy/bootstrap)
214
237
  client.test.ts
215
238
  db.test.ts
216
- setup-auth.test.ts
217
239
  tools/
218
240
  auth.test.ts
219
241
  sync.test.ts
package/SKILL.md CHANGED
@@ -56,28 +56,29 @@ Then add to `.mcp.json`:
56
56
 
57
57
  Or use a `.env` file in the project directory with `CK_COOKIES=<value>`.
58
58
 
59
- ### Getting CK_COOKIES
59
+ ### Getting CK_COOKIES (optional)
60
60
 
61
- **Scripted (recommended source install):**
62
- ```bash
63
- npm run auth # prints the Cookie header to the console
64
- npm run auth -- .env # writes CK_COOKIES=<header> to .env
65
- ```
61
+ Three onboarding paths, in priority order:
62
+
63
+ **1. fetchproxy extension (easiest — no env vars):** Install the [fetchproxy 0.3.0 extension](https://github.com/chrischall/fetchproxy), sign into creditkarma.com once, and leave `CK_COOKIES` **unset**. The MCP reads HttpOnly `CKAT` + `CKTRKID` cookies on the first tool call via `chrome.cookies.get`, then operates direct-to-API from Node.
66
64
 
67
- Launches Chrome with a dedicated profile, waits for sign-in at creditkarma.com, then captures the full session Cookie header (CKAT carries the access + refresh JWTs; CKTRKID and friends are needed by the refresh endpoint). Use the printed value with Claude Desktop / MCPB, or the `.env` form when running from source.
65
+ **2. ck_set_session MCP tool:** From within Claude, call `ck_set_session` with a Cookie header you copied from DevTools (see below). The tool persists it to `.env`.
68
66
 
69
- **Manual (DevTools):**
67
+ **3. Manual (DevTools):**
70
68
  1. Log in to [creditkarma.com](https://www.creditkarma.com) in Chrome
71
69
  2. DevTools → **Network** → any creditkarma.com request → **Request Headers**
72
70
  3. Right-click the `cookie` header → **Copy value**
71
+ 4. Paste into `CK_COOKIES` in your Claude config
73
72
 
74
73
  ## Authentication
75
74
 
76
- Call `ck_set_session` with your Cookie header to store credentials and enable auto-refresh.
75
+ The MCP handles auth automatically once any of the three paths is configured.
77
76
 
78
77
  - Access token: ~15 min TTL, auto-refreshed transparently
79
78
  - Refresh token: ~8 hours TTL
80
- - When expired: re-run `npm run auth` (or grab a fresh Cookie header) and call `ck_set_session`
79
+ - When expired:
80
+ - **fetchproxy path:** sign back into creditkarma.com — the MCP reads fresh cookies on the next tool call
81
+ - **env-var / ck_set_session path:** grab a fresh Cookie header from DevTools and update `CK_COOKIES` (or call `ck_set_session` again)
81
82
 
82
83
  ## Tools
83
84
 
@@ -104,8 +105,8 @@ Call `ck_set_session` with your Cookie header to store credentials and enable au
104
105
  ## Workflows
105
106
 
106
107
  **First-time setup:**
107
- 1. Run `npm run auth` (or grab the Cookie header manually from a creditkarma.com request in DevTools)
108
- 2. Paste into `CK_COOKIES` env var, or call `ck_set_session(cookies)` from within Claude
108
+ 1. Easiest: install the [fetchproxy extension](https://github.com/chrischall/fetchproxy), sign into creditkarma.com, leave `CK_COOKIES` unset.
109
+ 2. Or: copy the Cookie header from DevTools and either set `CK_COOKIES` in your config or call `ck_set_session(cookies)` from within Claude.
109
110
  3. `ck_sync_transactions` → initial full sync
110
111
 
111
112
  **Regular use:**
package/dist/auth.js ADDED
@@ -0,0 +1,198 @@
1
+ // ────────────────────────────────────────────────────────────────────────────
2
+ // Auth resolution — Pattern A template
3
+ // ────────────────────────────────────────────────────────────────────────────
4
+ //
5
+ // Mirrors the canonical "browser-bootstrap + Node-direct" shape from
6
+ // ofw-mcp/src/auth.ts. Other MCPs in this family (resy-mcp, opentable-mcp,
7
+ // signupgenius-mcp, zola-mcp, …) use the same selector — keep the structure
8
+ // flat, the path-selection explicit, and the error messages actionable.
9
+ //
10
+ // THE PATHS, in priority order:
11
+ //
12
+ // 1. CK_COOKIES env var (existing behavior)
13
+ // A full Cookie header (e.g. `CKTRKID=...; CKAT=eyJ...%3BeyJ...; ...`)
14
+ // from a signed-in creditkarma.com request. The CKAT cookie contains
15
+ // `<accessJWT>%3B<refreshJWT>` URL-encoded, which the caller parses.
16
+ // Legacy users keep working without action.
17
+ //
18
+ // 2. Cached session from `ck_set_session` (existing behavior)
19
+ // The MCP tool `ck_set_session` accepts a pasted Cookie header and
20
+ // persists it to .env as CK_COOKIES — so once it's been called, this
21
+ // path collapses into path 1 on subsequent runs.
22
+ //
23
+ // 3. fetchproxy fallback (new)
24
+ // When no Cookie header is set, lift the user's session out of their
25
+ // signed-in creditkarma.com browser tab via the fetchproxy 0.3.0
26
+ // extension. `@fetchproxy/bootstrap` spins up a one-shot WebSocket
27
+ // bridge, asks the extension for the `CKAT` and `CKTRKID` cookies via
28
+ // `chrome.cookies.get`, then closes the bridge. The synthesized
29
+ // Cookie header has the same shape that ck_set_session produces, so
30
+ // the rest of the stack consumes it without branching.
31
+ //
32
+ // All subsequent API calls go out via plain Node `fetch()` —
33
+ // fetchproxy is NOT in the request hot path. Token refresh
34
+ // (`POST /member/oauth2/refresh`) is also a plain Node fetch.
35
+ //
36
+ // Users opt out with CK_DISABLE_FETCHPROXY=1 (anyone who wants the
37
+ // old behavior of "fail loudly when creds are missing").
38
+ //
39
+ // 4. Error
40
+ // Nothing to authenticate with. We throw a message that names all
41
+ // three onboarding paths so the user can pick whichever fits.
42
+ //
43
+ // Why fetchproxy is only a one-shot read:
44
+ // The bootstrap call snapshots the CKAT + CKTRKID cookies and returns.
45
+ // The MCP then operates from Node with direct fetch — latency and
46
+ // reliability are not coupled to the browser bridge for normal tool
47
+ // calls. If the access JWT inside CKAT expires, the refresh flow runs
48
+ // in pure Node against `creditkarma.com/member/oauth2/refresh`. If
49
+ // that 403s (Akamai gate / expired refresh JWT), the user re-signs into
50
+ // creditkarma.com in the browser and the next MCP run re-reads the
51
+ // fresh cookies.
52
+ //
53
+ // Testability:
54
+ // - `@fetchproxy/bootstrap` is mocked at the module boundary in tests.
55
+ // - This module exposes a single async `resolveAuth()` that returns a
56
+ // Cookie header string + a source label. Callers treat the cookies
57
+ // value as opaque — the existing parser in `src/index.ts` /
58
+ // `src/tools/auth.ts` extracts the CKAT JWTs the same way it does
59
+ // today.
60
+ import { bootstrap } from '@fetchproxy/bootstrap';
61
+ import pkg from '../package.json' with { type: 'json' };
62
+ import { extractCookieValue } from './client.js';
63
+ /**
64
+ * Read an env var, trim, and treat blank / `${UNEXPANDED}` placeholders as
65
+ * unset. Defends against MCP hosts that pass `.mcp.json` env blocks through
66
+ * without variable expansion.
67
+ */
68
+ function readEnv(key) {
69
+ const raw = process.env[key];
70
+ if (typeof raw !== 'string')
71
+ return undefined;
72
+ const trimmed = raw.trim();
73
+ if (trimmed.length === 0)
74
+ return undefined;
75
+ if (trimmed === 'undefined' || trimmed === 'null')
76
+ return undefined;
77
+ if (/^\$\{[^}]*\}$/.test(trimmed))
78
+ return undefined;
79
+ return trimmed;
80
+ }
81
+ /** True if the user has explicitly disabled the fetchproxy fallback. */
82
+ function fetchproxyDisabled() {
83
+ const raw = readEnv('CK_DISABLE_FETCHPROXY');
84
+ if (raw === undefined)
85
+ return false;
86
+ return ['1', 'true', 'yes', 'on'].includes(raw.toLowerCase());
87
+ }
88
+ /**
89
+ * Resolve CK auth using the path priority described at the top of this
90
+ * file. Throws with an actionable error message when no path succeeds.
91
+ *
92
+ * Callers should treat the return value as opaque credentials — they
93
+ * should not branch on `source`. The field exists for logging / future
94
+ * cache-keying only.
95
+ */
96
+ export async function resolveAuth() {
97
+ // ── Path 1: CK_COOKIES env var (unchanged from pre-fetchproxy behavior).
98
+ const envCookies = readEnv('CK_COOKIES');
99
+ if (envCookies) {
100
+ return { cookies: envCookies, source: 'env' };
101
+ }
102
+ // ── Path 2: fetchproxy fallback (new).
103
+ // (Path 2 — cached session from ck_set_session — also lands here at the
104
+ // env-var step on subsequent runs, since that tool writes CK_COOKIES
105
+ // to .env. So this branch only fires when neither env var nor cache
106
+ // has been seeded.)
107
+ if (!fetchproxyDisabled()) {
108
+ try {
109
+ const session = await bootstrap({
110
+ serverName: pkg.name,
111
+ version: pkg.version,
112
+ // CK serves www.creditkarma.com (web) and api.creditkarma.com
113
+ // (GraphQL). Both share the apex domain; the extension matches on
114
+ // suffix so listing the apex covers any subdomain.
115
+ domains: ['creditkarma.com'],
116
+ declare: {
117
+ // CKAT contains the access + refresh JWTs joined by `%3B`. CKTRKID
118
+ // is sent as the `ck-cookie-id` header on refresh requests; without
119
+ // it the refresh endpoint 403s. Both are HttpOnly — invisible to
120
+ // page JS — but fetchproxy 0.3.0's `read_cookies` uses
121
+ // `chrome.cookies.get` which sees HttpOnly cookies.
122
+ cookies: ['CKAT', 'CKTRKID'],
123
+ localStorage: [],
124
+ sessionStorage: [],
125
+ captureHeaders: [],
126
+ },
127
+ });
128
+ const ckat = session.cookies['CKAT'];
129
+ const cktrkid = session.cookies['CKTRKID'];
130
+ if (!ckat) {
131
+ throw new Error('CKAT cookie missing on creditkarma.com. ' +
132
+ 'Sign into creditkarma.com in your browser (with the fetchproxy extension installed) and retry.');
133
+ }
134
+ if (!cktrkid) {
135
+ throw new Error('CKTRKID cookie missing on creditkarma.com. ' +
136
+ 'Sign into creditkarma.com in your browser (with the fetchproxy extension installed) and retry.');
137
+ }
138
+ // Synthesize a Cookie header identical in shape to what `ck_set_session`
139
+ // accepts. The existing parser in `src/index.ts` / `src/tools/auth.ts`
140
+ // extracts CKAT and splits its `<accessJWT>%3B<refreshJWT>` payload
141
+ // without caring how the header was assembled.
142
+ const cookies = `CKTRKID=${cktrkid}; CKAT=${ckat}`;
143
+ return { cookies, source: 'fetchproxy' };
144
+ }
145
+ catch (e) {
146
+ const msg = e instanceof Error ? e.message : String(e);
147
+ throw new Error(`CK auth: no CK_COOKIES set, and fetchproxy fallback failed: ${msg}`);
148
+ }
149
+ }
150
+ // ── Path 4: nothing configured. Surface all three fixes side-by-side so
151
+ // the user can pick whichever fits their setup.
152
+ throw new Error('CK auth: set CK_COOKIES, ' +
153
+ 'or call the ck_set_session MCP tool with a Cookie header, ' +
154
+ 'or install the fetchproxy extension and sign into creditkarma.com ' +
155
+ '(unset CK_DISABLE_FETCHPROXY if it is set).');
156
+ }
157
+ /**
158
+ * Parse a Cookie header into the CK_COOKIES → (accessToken, refreshToken)
159
+ * shape, mirroring `src/index.ts` and `src/tools/auth.ts`. The CKAT cookie
160
+ * value is `<accessJWT>%3B<refreshJWT>` URL-encoded; we split on either
161
+ * the encoded or literal semicolon.
162
+ *
163
+ * Exported so both `src/index.ts` (startup) and `loadAuthIntoClient()`
164
+ * (lazy bootstrap) can share one parser. Returns nulls (not errors) when
165
+ * the input doesn't contain a CKAT — the caller decides whether absence
166
+ * is fatal.
167
+ */
168
+ export function parseCookieHeader(cookies) {
169
+ const ckat = extractCookieValue(cookies, 'CKAT') ?? cookies.trim();
170
+ const parts = ckat.replace('%3B', ';').split(';');
171
+ const accessToken = parts[0]?.trim() || null;
172
+ const refreshToken = parts[1]?.trim() || null;
173
+ return { accessToken, refreshToken };
174
+ }
175
+ /**
176
+ * Resolve CK auth via `resolveAuth()` and apply the result to a client.
177
+ *
178
+ * Used by tool handlers on the first request that needs auth but finds no
179
+ * credentials on the client — i.e. the user didn't set CK_COOKIES, didn't
180
+ * call ck_set_session, and the fetchproxy extension is the last hope.
181
+ *
182
+ * If `resolveAuth()` lands on the env-var path (path 1) the cookies are
183
+ * applied with no network round-trip. If it lands on fetchproxy (path 3)
184
+ * the bootstrap call snapshots the browser session once; afterwards the
185
+ * client has fresh CKAT + CKTRKID and the normal `refreshAccessToken()`
186
+ * flow takes over.
187
+ */
188
+ export async function loadAuthIntoClient(client) {
189
+ const { cookies } = await resolveAuth();
190
+ const { accessToken, refreshToken } = parseCookieHeader(cookies);
191
+ if (!accessToken) {
192
+ throw new Error('CK auth: resolved cookies did not contain a CKAT token.');
193
+ }
194
+ client.setToken(accessToken);
195
+ if (refreshToken)
196
+ client.setRefreshToken(refreshToken);
197
+ client.setCookies(cookies);
198
+ }