@chrischall/mcp-utils 0.1.0
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 +235 -0
- package/dist/auth/index.d.ts +223 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +267 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/config/index.d.ts +86 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +121 -0
- package/dist/config/index.js.map +1 -0
- package/dist/errors/index.d.ts +90 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +157 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/fetchproxy/index.d.ts +156 -0
- package/dist/fetchproxy/index.d.ts.map +1 -0
- package/dist/fetchproxy/index.js +197 -0
- package/dist/fetchproxy/index.js.map +1 -0
- package/dist/html/index.d.ts +142 -0
- package/dist/html/index.d.ts.map +1 -0
- package/dist/html/index.js +321 -0
- package/dist/html/index.js.map +1 -0
- package/dist/http/index.d.ts +202 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +341 -0
- package/dist/http/index.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/response/index.d.ts +22 -0
- package/dist/response/index.d.ts.map +1 -0
- package/dist/response/index.js +61 -0
- package/dist/response/index.js.map +1 -0
- package/dist/server/index.d.ts +109 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +95 -0
- package/dist/server/index.js.map +1 -0
- package/dist/session/index.d.ts +233 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +404 -0
- package/dist/session/index.js.map +1 -0
- package/dist/test/index.d.ts +124 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +181 -0
- package/dist/test/index.js.map +1 -0
- package/dist/zod/index.d.ts +130 -0
- package/dist/zod/index.d.ts.map +1 -0
- package/dist/zod/index.js +184 -0
- package/dist/zod/index.js.map +1 -0
- package/package.json +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# @chrischall/mcp-utils
|
|
2
|
+
|
|
3
|
+
Shared scaffolding for the **chrischall MCP fleet** — the generic MCP glue
|
|
4
|
+
hoisted out of ~19 sibling servers so each one no longer reimplements server
|
|
5
|
+
bootstrap, tool-result formatting, helpful errors, hardened env/config, a bearer
|
|
6
|
+
API-client kit, zod atoms, session registries, a fetchproxy transport adapter,
|
|
7
|
+
auth resolver skeletons, an in-memory test harness, and opt-in HTML helpers.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @chrischall/mcp-utils
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer dependencies: `@modelcontextprotocol/sdk` and `zod`. The `@fetchproxy/server`
|
|
14
|
+
and `node-html-parser` peers are **optional** — only needed if you import the
|
|
15
|
+
`/fetchproxy` or `/html` subpaths respectively. Their declared range is `*` so a
|
|
16
|
+
consumer pinning any version installs cleanly; the real requirement is enforced
|
|
17
|
+
at the subpath: **`/fetchproxy` needs `@fetchproxy/server` >= 0.11** (it
|
|
18
|
+
re-exports APIs added there — `withDeadline`, `backoffDelayMs`, `BRIDGE_CONCURRENCY`,
|
|
19
|
+
the bridge-error classifier). MCPs on older `@fetchproxy/server` can use the core
|
|
20
|
+
barrel freely; adopt `/fetchproxy` only after bumping to 0.11+.
|
|
21
|
+
|
|
22
|
+
## Entry points
|
|
23
|
+
|
|
24
|
+
The core building blocks are re-exported from the package root. Heavier or
|
|
25
|
+
optional-dependency modules are published as **subpath entries** to keep the core
|
|
26
|
+
import light:
|
|
27
|
+
|
|
28
|
+
| Import | Contents |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `@chrischall/mcp-utils` | core barrel: `server` + `response` + `errors` + `config` + `http` + `zod` + `auth` |
|
|
31
|
+
| `@chrischall/mcp-utils/session` | session registry, session store, token manager |
|
|
32
|
+
| `@chrischall/mcp-utils/fetchproxy` | fetchproxy transport adapter, bot-wall / retry / concurrency helpers |
|
|
33
|
+
| `@chrischall/mcp-utils/html` | opt-in HTML scraping helpers (needs `node-html-parser`) |
|
|
34
|
+
| `@chrischall/mcp-utils/test` | in-memory test harness for tool registration |
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createMcpServer, textResult, requireEnvVar } from '@chrischall/mcp-utils';
|
|
38
|
+
import { createSessionRegistry } from '@chrischall/mcp-utils/session';
|
|
39
|
+
import { createFetchproxyTransport } from '@chrischall/mcp-utils/fetchproxy';
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Modules
|
|
43
|
+
|
|
44
|
+
### `server` — bootstrap & lifecycle
|
|
45
|
+
|
|
46
|
+
`createMcpServer`, `runMcp`, `withGracefulShutdown`.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { runMcp, textResult } from '@chrischall/mcp-utils';
|
|
50
|
+
|
|
51
|
+
await runMcp({
|
|
52
|
+
name: 'my-mcp',
|
|
53
|
+
version: '1.0.0',
|
|
54
|
+
register: (server) => {
|
|
55
|
+
server.tool('ping', {}, async () => textResult({ ok: true }));
|
|
56
|
+
},
|
|
57
|
+
// shutdown: { onSignal: () => client.close() },
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`runMcp` wires the server to a stdio transport and installs `SIGINT`/`SIGTERM`
|
|
62
|
+
handlers via `withGracefulShutdown`. Use `createMcpServer` directly if you need
|
|
63
|
+
the server instance without connecting a transport.
|
|
64
|
+
|
|
65
|
+
### `response` — tool-result formatting
|
|
66
|
+
|
|
67
|
+
`textResult` / `jsonResult` (alias), `rawTextResult`, `imageResult`,
|
|
68
|
+
`errorResult`, `flattenJsonApi`.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { textResult, errorResult, flattenJsonApi } from '@chrischall/mcp-utils';
|
|
72
|
+
|
|
73
|
+
return textResult({ items }); // pretty-printed JSON
|
|
74
|
+
return errorResult('not found'); // { isError: true }
|
|
75
|
+
return textResult(flattenJsonApi(payload)); // collapse JSON:API envelopes
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `errors` — helpful errors
|
|
79
|
+
|
|
80
|
+
`McpToolError` and its subclasses (`SessionNotAuthenticatedError`,
|
|
81
|
+
`BotWallError`, `RateLimitError`, `UnreachableError`, `ModeMismatchError`),
|
|
82
|
+
plus `createHelpfulError`, `wrapToolError`, `truncateErrorMessage`, and
|
|
83
|
+
`messageOf`. This core module has **no runtime dependencies** — the fetchproxy
|
|
84
|
+
typed-error hierarchy (`Fetchproxy*Error`) and the `classifyBridgeError`
|
|
85
|
+
discriminator live in the [`/fetchproxy`](#fetchproxy) subpath instead, so
|
|
86
|
+
bearer-only MCPs can import the core barrel without installing
|
|
87
|
+
`@fetchproxy/server`.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { wrapToolError, SessionNotAuthenticatedError } from '@chrischall/mcp-utils';
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
if (!token) throw new SessionNotAuthenticatedError({ hint: 'run the login tool first' });
|
|
94
|
+
} catch (err) {
|
|
95
|
+
throw wrapToolError('my_tool', err);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every error carries an optional `hint` — a "here's how to fix it" string the
|
|
100
|
+
tool surface can show the user.
|
|
101
|
+
|
|
102
|
+
### `config` — hardened env/config
|
|
103
|
+
|
|
104
|
+
`readEnvVar`, `requireEnvVar`, `parseBoolEnv`, `expandPath`, `loadDotenvSafely`.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { requireEnvVar, parseBoolEnv, expandPath } from '@chrischall/mcp-utils';
|
|
108
|
+
|
|
109
|
+
const apiKey = requireEnvVar('MY_API_KEY');
|
|
110
|
+
const debug = parseBoolEnv('MY_DEBUG', { default: false });
|
|
111
|
+
const home = expandPath('~/.config/my-mcp');
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`loadDotenvSafely` is a no-throw `.env` loader (returns `false` instead of
|
|
115
|
+
failing when the file is absent).
|
|
116
|
+
|
|
117
|
+
### `http` — bearer API-client kit
|
|
118
|
+
|
|
119
|
+
`createApiClient` plus building blocks: `buildQueryString`, `buildOptionalBody`,
|
|
120
|
+
`formatApiError`, `parseLinkHeader`, `parseCookieJar`, JWT helpers
|
|
121
|
+
(`decodeJwtExp`, `decodeJwtSessionId`, `validateJwtExpiry`), and the
|
|
122
|
+
`UnauthorizedError` / `RateLimitedError` classes.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { createApiClient } from '@chrischall/mcp-utils';
|
|
126
|
+
|
|
127
|
+
const api = createApiClient({
|
|
128
|
+
baseUrl: 'https://api.example.com',
|
|
129
|
+
getToken: () => store.currentToken(), // resolved per-request; sync or async
|
|
130
|
+
serviceName: 'Example',
|
|
131
|
+
retry: { count: 1, delayMs: 2000 }, // fleet-wide "retry once after 2s" default
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const data = await api.get('/v1/things', { query: { page: 2 } });
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `zod` — schema atoms
|
|
138
|
+
|
|
139
|
+
Reusable schemas (`PositiveInt`, `NonNegInt`, `NonEmptyString`, `IsoDate`,
|
|
140
|
+
`IsoTime`, `schemaOrigin`, `schemaConfirm`), pagination helpers
|
|
141
|
+
(`paginationSchema`, `pageSchema`, `calculateOffset`), tool-annotation builders
|
|
142
|
+
(`toolAnnotations`), and time normalizers (`extractTime`, `normalizeTime`).
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { paginationSchema, calculateOffset, toolAnnotations } from '@chrischall/mcp-utils';
|
|
146
|
+
|
|
147
|
+
const inputSchema = { ...paginationSchema, q: NonEmptyString };
|
|
148
|
+
const offset = calculateOffset(page, size);
|
|
149
|
+
const annotations = toolAnnotations({ readOnly: true });
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `auth` — auth resolver skeletons
|
|
153
|
+
|
|
154
|
+
`createAuthResolver`, `resolveAuthPattern`, `sessionLoginFlow`,
|
|
155
|
+
`createOAuth2Refresher`, and the supporting `FetchproxySession` /
|
|
156
|
+
`AuthPattern` types.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { createAuthResolver, createOAuth2Refresher } from '@chrischall/mcp-utils';
|
|
160
|
+
|
|
161
|
+
const resolver = createAuthResolver({ /* ... */ });
|
|
162
|
+
const refresh = createOAuth2Refresher({ /* ... */ });
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `session` — session registry & token manager *(subpath)*
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import {
|
|
169
|
+
createSessionRegistry,
|
|
170
|
+
registerSessionTools,
|
|
171
|
+
TokenManager,
|
|
172
|
+
} from '@chrischall/mcp-utils/session';
|
|
173
|
+
|
|
174
|
+
const registry = createSessionRegistry();
|
|
175
|
+
registerSessionTools(server, { registry /* ... */ });
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Includes `SessionStore`, `normalizeOrigin`, `AuthMode`, and `TokenManager`
|
|
179
|
+
(with `TOKEN_REFRESH_SKEW_MS` for proactive refresh).
|
|
180
|
+
|
|
181
|
+
### `fetchproxy` — transport adapter *(subpath, optional peer)*
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import {
|
|
185
|
+
createFetchproxyTransport,
|
|
186
|
+
createBootstrapOpts,
|
|
187
|
+
mapWithConcurrency,
|
|
188
|
+
TokenBucket,
|
|
189
|
+
classifyBotWall,
|
|
190
|
+
} from '@chrischall/mcp-utils/fetchproxy';
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Wraps `@fetchproxy/server` with the fleet's transport, bot-wall classification,
|
|
194
|
+
deadline/retry, token-bucket rate limiting, and bounded-concurrency helpers, and
|
|
195
|
+
re-exports the fetchproxy typed-error hierarchy.
|
|
196
|
+
|
|
197
|
+
### `html` — scraping helpers *(subpath, optional peer)*
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import {
|
|
201
|
+
parsePropertyTable,
|
|
202
|
+
findLinksUnderHeading,
|
|
203
|
+
extractJsonFromHtml,
|
|
204
|
+
extractPlainTextFromHtml,
|
|
205
|
+
} from '@chrischall/mcp-utils/html';
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Requires the optional `node-html-parser` peer. Also provides `urlToPath`,
|
|
209
|
+
`locationToSlug`, and `buildIdExtractor`.
|
|
210
|
+
|
|
211
|
+
### `test` — in-memory test harness *(subpath)*
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { createTestHarness, parseToolResult } from '@chrischall/mcp-utils/test';
|
|
215
|
+
|
|
216
|
+
const harness = createTestHarness();
|
|
217
|
+
register(harness.server);
|
|
218
|
+
const result = await harness.call('ping', {});
|
|
219
|
+
expect(parseToolResult(result)).toEqual({ ok: true });
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Also includes `versionSyncTest`, `mockFetchproxyBootstrap`, `setupClientMocks`,
|
|
223
|
+
and `makeBootstrapResult`.
|
|
224
|
+
|
|
225
|
+
## Development
|
|
226
|
+
|
|
227
|
+
```sh
|
|
228
|
+
npm run build # tsc -b → dist/
|
|
229
|
+
npm test # vitest run
|
|
230
|
+
npm run test:watch # vitest (watch mode)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
MIT
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth resolver skeletons — the shared *shape* of the credential-resolution
|
|
3
|
+
* logic duplicated across the fleet (resy / opentable / ofw / zola /
|
|
4
|
+
* signupgenius / creditkarma / canvas / infinitecampus auth.ts).
|
|
5
|
+
*
|
|
6
|
+
* This module owns the **skeleton** only. Per-site parameters (which env var,
|
|
7
|
+
* which cookies to declare, how to parse the session blob, which CSRF input to
|
|
8
|
+
* scrape) are *injected*; nothing here branches on site identity. Site-specific
|
|
9
|
+
* OAuth choreographies (e.g. Skylight's `/auth/session` → `/oauth/authorize` →
|
|
10
|
+
* `/oauth/token` dance) stay per-MCP — they are not a shared shape.
|
|
11
|
+
*
|
|
12
|
+
* Four pieces:
|
|
13
|
+
* - {@link createAuthResolver} — the three-path resolver
|
|
14
|
+
* (env credential → fetchproxy one-shot read → actionable error).
|
|
15
|
+
* - {@link resolveAuthPattern} — the four-path variant
|
|
16
|
+
* (token → OAuth → session-scrape → fetchproxy), each path an injected
|
|
17
|
+
* resolver tried in priority order.
|
|
18
|
+
* - {@link sessionLoginFlow} — CSRF-scrape + cookie POST + success-marker,
|
|
19
|
+
* the shared CSRF+cookie login primitive (canvas / IC / ofw / signupgenius).
|
|
20
|
+
* - {@link createOAuth2Refresher} — an OAuth2 `refresh_token`-grant refresher
|
|
21
|
+
* with optional retry and in-flight race-safety.
|
|
22
|
+
*
|
|
23
|
+
* Security posture: env reads go through the hardened {@link readEnvVar}
|
|
24
|
+
* (placeholder / `'null'` / `'undefined'` suppression); thrown errors never
|
|
25
|
+
* echo the offending secret value and run upstream bodies through
|
|
26
|
+
* {@link truncateErrorMessage} (redaction + truncation) before surfacing.
|
|
27
|
+
*/
|
|
28
|
+
import { type EnvSource } from '../config/index.js';
|
|
29
|
+
/** A `@fetchproxy/bootstrap` session blob: declared cookies / storage, by key. */
|
|
30
|
+
export interface FetchproxySession {
|
|
31
|
+
cookies: Record<string, string>;
|
|
32
|
+
localStorage?: Record<string, string>;
|
|
33
|
+
sessionStorage?: Record<string, string>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The injected `@fetchproxy/bootstrap`-shaped function. Kept as a parameter (not
|
|
37
|
+
* a hard import) so the heavy bridge dep stays out of this module and tests can
|
|
38
|
+
* mock the module boundary, exactly as every fleet `auth.ts` does today.
|
|
39
|
+
*/
|
|
40
|
+
export type BootstrapFn = (opts: unknown) => Promise<FetchproxySession>;
|
|
41
|
+
/** Result of {@link createAuthResolver}'s resolver — opaque credential + provenance. */
|
|
42
|
+
export interface ResolvedCredential {
|
|
43
|
+
/** The resolved credential (token / cookie header / refresh JWT — opaque). */
|
|
44
|
+
credential: string;
|
|
45
|
+
/** Which path produced it. Diagnostics / cache-keying only — do not branch on it. */
|
|
46
|
+
source: 'env' | 'fetchproxy';
|
|
47
|
+
}
|
|
48
|
+
/** Options for {@link createAuthResolver}. */
|
|
49
|
+
export interface AuthResolverOptions {
|
|
50
|
+
/** Env var holding the credential when the user supplies it directly (path 1). */
|
|
51
|
+
envVar: string;
|
|
52
|
+
/**
|
|
53
|
+
* Env var that, when truthy, disables the fetchproxy fallback (path 2). When
|
|
54
|
+
* omitted, the fallback is always attempted. Mirrors `*_DISABLE_FETCHPROXY`.
|
|
55
|
+
*/
|
|
56
|
+
disableEnvVar?: string;
|
|
57
|
+
/** Injected `@fetchproxy/bootstrap` function (mocked at the boundary in tests). */
|
|
58
|
+
bootstrap: BootstrapFn;
|
|
59
|
+
/** The opts object passed verbatim to {@link bootstrap} (domains, declare, …). */
|
|
60
|
+
bootstrapOptions: unknown;
|
|
61
|
+
/**
|
|
62
|
+
* Lift the credential out of the fetchproxy session blob. Returns the
|
|
63
|
+
* credential string, or `undefined`/`''` when the signed-in tab didn't carry
|
|
64
|
+
* it (→ surfaced as a "sign in" error).
|
|
65
|
+
*/
|
|
66
|
+
parseTokens: (session: FetchproxySession) => string | undefined;
|
|
67
|
+
/** Human-readable service name for the not-signed-in error (e.g. "Zola"). */
|
|
68
|
+
serviceName?: string;
|
|
69
|
+
/** Host to point the user at when the browser session is missing (e.g. "zola.com"). */
|
|
70
|
+
signInHost?: string;
|
|
71
|
+
/** Env source. Defaults to {@link process.env}. */
|
|
72
|
+
env?: EnvSource;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build the canonical **three-path** auth resolver:
|
|
76
|
+
*
|
|
77
|
+
* 1. **Env credential** — `envVar` set (after hardened {@link readEnvVar}
|
|
78
|
+
* sanitization) → returned directly, no network.
|
|
79
|
+
* 2. **fetchproxy one-shot read** — unless `disableEnvVar` is truthy, call the
|
|
80
|
+
* injected `bootstrap` to snapshot the user's signed-in browser session,
|
|
81
|
+
* then `parseTokens` to extract the credential. fetchproxy is invoked once;
|
|
82
|
+
* it is never in the hot path.
|
|
83
|
+
* 3. **Actionable error** — nothing configured: an error naming the env var
|
|
84
|
+
* and the sign-in fallback so the user can pick a fix.
|
|
85
|
+
*
|
|
86
|
+
* The returned `source` is for diagnostics; callers must treat the credential
|
|
87
|
+
* as opaque and not branch on it.
|
|
88
|
+
*/
|
|
89
|
+
export declare function createAuthResolver(opts: AuthResolverOptions): () => Promise<ResolvedCredential>;
|
|
90
|
+
/** Result of a {@link resolveAuthPattern} path resolver — opaque credential + provenance. */
|
|
91
|
+
export interface PatternResult {
|
|
92
|
+
/** The resolved credential (bearer / cookie header — opaque to the caller). */
|
|
93
|
+
credential: string;
|
|
94
|
+
/** Which path produced it. Diagnostics only — callers should not branch on it. */
|
|
95
|
+
source: string;
|
|
96
|
+
}
|
|
97
|
+
/** A single path resolver. Returning a value claims the path; throwing aborts. */
|
|
98
|
+
export type PathResolver = () => Promise<PatternResult>;
|
|
99
|
+
/**
|
|
100
|
+
* The four ordered paths of the "Pattern A template" (canvas / IC). A path is
|
|
101
|
+
* **configured** iff its resolver is provided; the *first* configured path, in
|
|
102
|
+
* this fixed priority order, runs. This is the only ordering — it never branches
|
|
103
|
+
* on which site is calling.
|
|
104
|
+
*/
|
|
105
|
+
export interface AuthPattern {
|
|
106
|
+
/** Path 1: a stateless personal-access-token credential. */
|
|
107
|
+
token?: PathResolver;
|
|
108
|
+
/** Path 2: an OAuth `refresh_token` grant. */
|
|
109
|
+
oauth?: PathResolver;
|
|
110
|
+
/** Path 3: a username/password session-scrape (CSRF + cookie login). */
|
|
111
|
+
sessionScrape?: PathResolver;
|
|
112
|
+
/** Path 4: a fetchproxy one-shot browser-session read. */
|
|
113
|
+
fetchproxy?: PathResolver;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Resolve auth via the four-path priority **token → OAuth → session-scrape →
|
|
117
|
+
* fetchproxy**. Runs the first *provided* resolver in that order (a missing
|
|
118
|
+
* resolver = an unconfigured path). A resolver that throws propagates — a
|
|
119
|
+
* partial-config error (the user's mistake) must surface, not silently fall
|
|
120
|
+
* through. Throws an actionable error when no path is configured at all.
|
|
121
|
+
*/
|
|
122
|
+
export declare function resolveAuthPattern(pattern: AuthPattern): Promise<PatternResult>;
|
|
123
|
+
/** Options for {@link sessionLoginFlow}. */
|
|
124
|
+
export interface SessionLoginOptions {
|
|
125
|
+
/** URL of the login page to GET (carries the CSRF input + sets a session cookie). */
|
|
126
|
+
loginUrl: string;
|
|
127
|
+
/** URL to POST the credentials to. */
|
|
128
|
+
postUrl: string;
|
|
129
|
+
/** Regex with one capture group that extracts the CSRF token from the page HTML. */
|
|
130
|
+
csrfRegex: RegExp;
|
|
131
|
+
/** Form field name the CSRF token is submitted under. Defaults to `'csrfToken'`. */
|
|
132
|
+
csrfField?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Cookie name that signals a successful login *and* is returned as `token`.
|
|
135
|
+
* Its presence after the POST is the success marker.
|
|
136
|
+
*/
|
|
137
|
+
tokenField: string;
|
|
138
|
+
/** Form field name the email/username is submitted under. Defaults to `'email'`. */
|
|
139
|
+
emailField?: string;
|
|
140
|
+
/** Form field name the password is submitted under. Defaults to `'password'`. */
|
|
141
|
+
passwordField?: string;
|
|
142
|
+
/** The user's email / username. */
|
|
143
|
+
email: string;
|
|
144
|
+
/** The user's password. */
|
|
145
|
+
password: string;
|
|
146
|
+
/** Extra static form fields to include in the POST body (per-site form params). */
|
|
147
|
+
extraFields?: Record<string, string>;
|
|
148
|
+
/** Header sent on both requests (e.g. a desktop `User-Agent`). */
|
|
149
|
+
userAgent?: string;
|
|
150
|
+
/** Injectable fetch (defaults to global `fetch`) — for tests. */
|
|
151
|
+
fetchImpl?: typeof fetch;
|
|
152
|
+
}
|
|
153
|
+
/** Result of {@link sessionLoginFlow}. */
|
|
154
|
+
export interface SessionLoginResult {
|
|
155
|
+
/** Value of the `tokenField` cookie set on a successful login. */
|
|
156
|
+
token: string;
|
|
157
|
+
/** Full `Cookie` header (deduped jar) for subsequent authenticated requests. */
|
|
158
|
+
cookies: string;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The shared CSRF + cookie login primitive (canvas / IC / ofw / signupgenius):
|
|
162
|
+
*
|
|
163
|
+
* 1. GET `loginUrl` — capture the session cookie(s) and scrape the CSRF token
|
|
164
|
+
* out of the page with `csrfRegex`.
|
|
165
|
+
* 2. POST `postUrl` (`application/x-www-form-urlencoded`) with the scraped CSRF
|
|
166
|
+
* token, credentials, and any `extraFields`, carrying the GET's cookies.
|
|
167
|
+
* 3. Merge `Set-Cookie`s from both responses (deduped, deletions dropped) and
|
|
168
|
+
* require the `tokenField` cookie as the success marker — its value is the
|
|
169
|
+
* returned `token`; its absence means the credentials were rejected.
|
|
170
|
+
*
|
|
171
|
+
* Per-site form parameters and the CSRF regex are injected; the flow itself is
|
|
172
|
+
* site-agnostic.
|
|
173
|
+
*/
|
|
174
|
+
export declare function sessionLoginFlow(opts: SessionLoginOptions): Promise<SessionLoginResult>;
|
|
175
|
+
/** Options for {@link createOAuth2Refresher}. */
|
|
176
|
+
export interface OAuth2RefresherOptions {
|
|
177
|
+
/** Token endpoint to POST the grant to. */
|
|
178
|
+
endpoint: string;
|
|
179
|
+
/** The refresh token to exchange. */
|
|
180
|
+
refreshToken: string;
|
|
181
|
+
/** OAuth2 grant type. Defaults to `'refresh_token'`. */
|
|
182
|
+
grantType?: string;
|
|
183
|
+
/** Extra form params (e.g. `client_id`, `client_secret`, `scope`). */
|
|
184
|
+
params?: Record<string, string>;
|
|
185
|
+
/**
|
|
186
|
+
* Retry policy for a failed exchange. `count` is *additional* attempts after
|
|
187
|
+
* the first; `delayMs` is the fixed wait between attempts. Omit to never retry.
|
|
188
|
+
*/
|
|
189
|
+
retry?: {
|
|
190
|
+
count: number;
|
|
191
|
+
delayMs: number;
|
|
192
|
+
};
|
|
193
|
+
/** Injectable fetch (defaults to global `fetch`) — for tests. */
|
|
194
|
+
fetchImpl?: typeof fetch;
|
|
195
|
+
}
|
|
196
|
+
/** Result of an {@link createOAuth2Refresher} exchange. */
|
|
197
|
+
export interface OAuth2RefreshResult {
|
|
198
|
+
/** The new access token. */
|
|
199
|
+
accessToken: string;
|
|
200
|
+
/** A rotated refresh token, when the server returned one. */
|
|
201
|
+
refreshToken?: string;
|
|
202
|
+
/** `expires_in` (seconds), when the server returned one. */
|
|
203
|
+
expiresIn?: number;
|
|
204
|
+
/** Absolute expiry (`now + expires_in`), when `expires_in` was present. */
|
|
205
|
+
expiresAt?: Date;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Build a race-safe OAuth2 `refresh_token`-grant refresher. The returned
|
|
209
|
+
* function POSTs the form-encoded grant to `endpoint` and parses the standard
|
|
210
|
+
* `{ access_token, refresh_token?, expires_in? }` body.
|
|
211
|
+
*
|
|
212
|
+
* Race-safety: concurrent calls share a single in-flight exchange (the
|
|
213
|
+
* canonical token-refresh-race guard — `skylight`/`canvas`/`creditkarma`/`zola`
|
|
214
|
+
* all hand-roll this). The in-flight promise is cleared once it settles, so a
|
|
215
|
+
* later refresh starts fresh and a *rejected* exchange does not poison the next
|
|
216
|
+
* caller.
|
|
217
|
+
*
|
|
218
|
+
* Errors run through {@link truncateErrorMessage} (redaction + truncation)
|
|
219
|
+
* before surfacing, so an upstream error body can't leak a bearer token or
|
|
220
|
+
* blow up a tool result.
|
|
221
|
+
*/
|
|
222
|
+
export declare function createOAuth2Refresher(opts: OAuth2RefresherOptions): () => Promise<OAuth2RefreshResult>;
|
|
223
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"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"}
|