@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.
Files changed (50) hide show
  1. package/README.md +235 -0
  2. package/dist/auth/index.d.ts +223 -0
  3. package/dist/auth/index.d.ts.map +1 -0
  4. package/dist/auth/index.js +267 -0
  5. package/dist/auth/index.js.map +1 -0
  6. package/dist/config/index.d.ts +86 -0
  7. package/dist/config/index.d.ts.map +1 -0
  8. package/dist/config/index.js +121 -0
  9. package/dist/config/index.js.map +1 -0
  10. package/dist/errors/index.d.ts +90 -0
  11. package/dist/errors/index.d.ts.map +1 -0
  12. package/dist/errors/index.js +157 -0
  13. package/dist/errors/index.js.map +1 -0
  14. package/dist/fetchproxy/index.d.ts +156 -0
  15. package/dist/fetchproxy/index.d.ts.map +1 -0
  16. package/dist/fetchproxy/index.js +197 -0
  17. package/dist/fetchproxy/index.js.map +1 -0
  18. package/dist/html/index.d.ts +142 -0
  19. package/dist/html/index.d.ts.map +1 -0
  20. package/dist/html/index.js +321 -0
  21. package/dist/html/index.js.map +1 -0
  22. package/dist/http/index.d.ts +202 -0
  23. package/dist/http/index.d.ts.map +1 -0
  24. package/dist/http/index.js +341 -0
  25. package/dist/http/index.js.map +1 -0
  26. package/dist/index.d.ts +23 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +23 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/response/index.d.ts +22 -0
  31. package/dist/response/index.d.ts.map +1 -0
  32. package/dist/response/index.js +61 -0
  33. package/dist/response/index.js.map +1 -0
  34. package/dist/server/index.d.ts +109 -0
  35. package/dist/server/index.d.ts.map +1 -0
  36. package/dist/server/index.js +95 -0
  37. package/dist/server/index.js.map +1 -0
  38. package/dist/session/index.d.ts +233 -0
  39. package/dist/session/index.d.ts.map +1 -0
  40. package/dist/session/index.js +404 -0
  41. package/dist/session/index.js.map +1 -0
  42. package/dist/test/index.d.ts +124 -0
  43. package/dist/test/index.d.ts.map +1 -0
  44. package/dist/test/index.js +181 -0
  45. package/dist/test/index.js.map +1 -0
  46. package/dist/zod/index.d.ts +130 -0
  47. package/dist/zod/index.d.ts.map +1 -0
  48. package/dist/zod/index.js +184 -0
  49. package/dist/zod/index.js.map +1 -0
  50. package/package.json +77 -0
@@ -0,0 +1,202 @@
1
+ /**
2
+ * `http` — bearer-auth API-client kit.
3
+ *
4
+ * Consolidates the ~100-line `client.ts` boilerplate copy-pasted across ~8 MCP
5
+ * servers (splitwise/tempo/ioffice/app-store-connect/zola): a bearer-auth fetch
6
+ * wrapper with one-shot 429 retry, 401 mapping, 204 handling, and redacted error
7
+ * formatting — plus the small URL/JWT/cookie utilities scattered across the
8
+ * fleet (canvas Link-header parsing, IC/signupgenius cookie jars, zola JWT
9
+ * decode).
10
+ *
11
+ * Security posture (design §"Bearer-token / error-message leakage"):
12
+ * - {@link formatApiError} runs every upstream body through the shared
13
+ * {@link truncateErrorMessage} (redaction THEN truncation) so bearer tokens
14
+ * and JWTs never reach a tool result, even when an upstream echoes the
15
+ * request back.
16
+ * - {@link createApiClient} never embeds the token in a thrown message; a 401
17
+ * yields a fixed "unauthorized" string, not the credential.
18
+ */
19
+ /** Retry policy for transient (HTTP 429) responses. */
20
+ export interface RetryPolicy {
21
+ /** Number of retries after the initial attempt. The fleet default is 1. */
22
+ count: number;
23
+ /** Delay before each retry, in milliseconds. The fleet default is 2000. */
24
+ delayMs: number;
25
+ }
26
+ /** Options for {@link createApiClient}. */
27
+ export interface ApiClientOptions {
28
+ /** Absolute base URL; request paths are appended verbatim. A trailing slash is trimmed. */
29
+ baseUrl: string;
30
+ /**
31
+ * Resolve the current bearer token. Called per request so the caller can
32
+ * refresh/rotate transparently. May be sync or async. Return `undefined`/`''`
33
+ * to send no `Authorization` header (e.g. cookie-authenticated APIs).
34
+ */
35
+ getToken: () => string | undefined | Promise<string | undefined>;
36
+ /**
37
+ * 429 retry policy. Defaults to `{ count: 1, delayMs: 2000 }` — the
38
+ * fleet-wide "retry once after 2s" behavior. Set `count: 0` to disable.
39
+ */
40
+ retry?: RetryPolicy;
41
+ /** Human name of the upstream service, used in error messages. Defaults to the host. */
42
+ serviceName?: string;
43
+ /** Injectable fetch (for tests). Defaults to the global `fetch`. */
44
+ fetchImpl?: typeof fetch;
45
+ /** Injectable sleep (for tests). Defaults to `setTimeout`. */
46
+ sleep?: (ms: number) => Promise<void>;
47
+ }
48
+ /** A request body and/or extra headers for a single call. */
49
+ export interface RequestOptions {
50
+ /** JSON-serialized into the request body when present. */
51
+ body?: unknown;
52
+ /** Extra request headers, merged over the defaults. */
53
+ headers?: Record<string, string>;
54
+ /** Query params appended via {@link buildQueryString} when present. */
55
+ query?: Record<string, unknown>;
56
+ }
57
+ /** The minimal client surface returned by {@link createApiClient}. */
58
+ export interface ApiClient {
59
+ /**
60
+ * Authenticated JSON request. Returns the parsed body, or `undefined` for a
61
+ * 204 / empty body. Throws on 401 (unauthorized), exhausted-429, and other
62
+ * non-2xx responses (with a redacted, truncated message).
63
+ */
64
+ fetchJson: <T = unknown>(method: string, path: string, opts?: RequestOptions) => Promise<T>;
65
+ /** Authenticated request returning the raw response body as text (e.g. HTML scrapes). */
66
+ fetchHtml: (method: string, path: string, opts?: RequestOptions) => Promise<string>;
67
+ }
68
+ /** Thrown for an upstream 401. Carries the status so callers can trigger a re-auth. */
69
+ export declare class UnauthorizedError extends Error {
70
+ readonly status = 401;
71
+ constructor(service: string);
72
+ }
73
+ /** Thrown when a 429 persists after the retry budget is exhausted. */
74
+ export declare class RateLimitedError extends Error {
75
+ readonly status = 429;
76
+ constructor(service: string);
77
+ }
78
+ /**
79
+ * Build a bearer-auth fetch client with one-shot 429 retry, 401 mapping, 204 /
80
+ * empty-body handling, and redacted error formatting.
81
+ *
82
+ * Consolidates the structurally-identical `client.ts#doRequest` across
83
+ * splitwise/tempo/ioffice/app-store-connect/zola. The retry/401/429 behavior is
84
+ * the hardened superset: 401 → {@link UnauthorizedError} (never echoing the
85
+ * token), 429 → sleep(`delayMs`) and replay up to `count` times then
86
+ * {@link RateLimitedError}, 204/empty → `undefined`, other non-2xx →
87
+ * {@link formatApiError}.
88
+ */
89
+ export declare function createApiClient(opts: ApiClientOptions): ApiClient;
90
+ /**
91
+ * Build a URL query string from a params object, returning `''` or
92
+ * `?k=v&k2=v2`. Skips `undefined`, `null`, and empty-string values; expands
93
+ * arrays into repeated keys (skipping null/undefined/empty array members); and
94
+ * percent-encodes keys and values.
95
+ *
96
+ * Consolidates the divergent `buildQueryString` / inline `URLSearchParams`
97
+ * variants across compass/redfin/zillow/homes/opentable/tempo/ioffice into one
98
+ * superset (array support + empty-string skipping + encoding).
99
+ */
100
+ export declare function buildQueryString(params: Record<string, unknown>): string;
101
+ /**
102
+ * Build a request body from `args`, including only the `optionalFields` that are
103
+ * actually present (not `undefined`). `null` is preserved — some APIs use it to
104
+ * clear a field — only `undefined` (i.e. "not provided") is dropped.
105
+ *
106
+ * Consolidates tempo's optional-field body builder: lets a tool forward a wide
107
+ * args object and emit a minimal PATCH/POST body.
108
+ */
109
+ export declare function buildOptionalBody<T extends Record<string, unknown>, K extends keyof T>(args: T, optionalFields: readonly K[]): Partial<Pick<T, K>>;
110
+ /** Options for {@link formatApiError}. */
111
+ export interface FormatApiErrorOptions {
112
+ /** Service name woven into the prefix. Defaults to `'API'`. */
113
+ service?: string;
114
+ /** Truncation budget for the upstream body. Defaults to the shared 500. */
115
+ max?: number;
116
+ }
117
+ /**
118
+ * Format a non-2xx upstream response into a single, client-safe error string:
119
+ * `"{service} error {status} for {METHOD} {path}: {body}"`.
120
+ *
121
+ * SECURITY: the upstream `errorText` is run through the shared
122
+ * {@link truncateErrorMessage} (redaction of `Bearer <token>` / JWTs FIRST,
123
+ * then truncation) so a raw body that echoes the request — or an upstream that
124
+ * leaks a token — never reaches the caller. The method is upper-cased; an
125
+ * empty/whitespace body is dropped entirely rather than printing a dangling
126
+ * colon.
127
+ */
128
+ export declare function formatApiError(status: number, method: string, path: string, errorText: string, opts?: FormatApiErrorOptions): string;
129
+ /** The RFC 5988 rels callers care about for pagination. */
130
+ export interface ParsedLinkHeader {
131
+ next?: string;
132
+ prev?: string;
133
+ first?: string;
134
+ last?: string;
135
+ /** Any other rels present, keyed by rel name. */
136
+ [rel: string]: string | undefined;
137
+ }
138
+ /**
139
+ * Parse an RFC 5988 `Link` header into a `{ rel: url }` map. Malformed entries
140
+ * are skipped; `rel="next"` and bare `rel=next` are both accepted. A missing or
141
+ * empty header yields `{}`.
142
+ *
143
+ * Consolidates canvas's pagination Link parser.
144
+ */
145
+ export declare function parseLinkHeader(header: string | null | undefined): ParsedLinkHeader;
146
+ /** A parsed Set-Cookie jar: deduplicated name→value plus a ready `Cookie` header. */
147
+ export interface CookieJar {
148
+ /** Surviving cookies, name → value (last value wins, deletions removed). */
149
+ cookies: Record<string, string>;
150
+ /** Pre-joined `name=value; name2=value2` string for the `Cookie` request header. */
151
+ cookieHeader: string;
152
+ }
153
+ /**
154
+ * Parse `Set-Cookie` headers into a deduplicated {@link CookieJar}.
155
+ *
156
+ * Login responses commonly send deletion markers (`Max-Age=0` or an epoch
157
+ * `Expires`) alongside real cookies; forwarding both the delete and set form of
158
+ * a name makes some upstreams (e.g. IC) reject the request. This parser:
159
+ * - drops deletion markers (`Max-Age=0`, epoch `Expires`),
160
+ * - drops empty-value cookies (clearing instructions),
161
+ * - deduplicates by name with **last value wins**, preserving order.
162
+ *
163
+ * Accepts the array from `Headers.getSetCookie()` (or a single joined string —
164
+ * which it splits defensively, though `getSetCookie()` is strongly preferred
165
+ * since commas inside `Expires` make string-splitting lossy).
166
+ *
167
+ * Consolidates the IC/signupgenius cookie-jar logic.
168
+ */
169
+ export declare function parseCookieJar(setCookieHeaders: string[] | string | null | undefined): CookieJar;
170
+ /**
171
+ * Decode a JWT's `exp` claim (seconds since epoch). Throws when the structure is
172
+ * invalid or `exp` is missing/non-numeric — the strict variant zola uses for the
173
+ * token it depends on. For a lenient probe, use {@link validateJwtExpiry}.
174
+ */
175
+ export declare function decodeJwtExp(token: string): number;
176
+ /**
177
+ * Best-effort extraction of a session id from a JWT payload, checking
178
+ * `session_id` then `sid`. Returns `null` for an undecodable token or absent
179
+ * claim (never throws) — the lenient variant zola uses for the WAF session
180
+ * header.
181
+ */
182
+ export declare function decodeJwtSessionId(token: string): string | null;
183
+ /** Result of {@link validateJwtExpiry}. */
184
+ export interface JwtExpiryStatus {
185
+ /** True when the token is past its `exp` (or undecodable / lacks `exp`). */
186
+ expired: boolean;
187
+ /** Seconds until expiry (negative once expired); omitted when undecodable. */
188
+ expiresIn?: number;
189
+ /** Human-readable caution when the token is expired or near expiry. */
190
+ warning?: string;
191
+ }
192
+ /**
193
+ * Non-throwing expiry probe for a bearer JWT. Returns `{ expired, expiresIn?,
194
+ * warning? }`. An undecodable token (or one lacking a numeric `exp`) is treated
195
+ * as `expired: true` with a warning — failing closed so a malformed token forces
196
+ * a refresh rather than being sent and bouncing as a 401.
197
+ *
198
+ * Near-expiry (within {@link NEAR_EXPIRY_SKEW_SEC}) yields a warning while still
199
+ * reporting `expired: false`, so callers can refresh proactively.
200
+ */
201
+ export declare function validateJwtExpiry(token: string, nowMs?: number): JwtExpiryStatus;
202
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,341 @@
1
+ /**
2
+ * `http` — bearer-auth API-client kit.
3
+ *
4
+ * Consolidates the ~100-line `client.ts` boilerplate copy-pasted across ~8 MCP
5
+ * servers (splitwise/tempo/ioffice/app-store-connect/zola): a bearer-auth fetch
6
+ * wrapper with one-shot 429 retry, 401 mapping, 204 handling, and redacted error
7
+ * formatting — plus the small URL/JWT/cookie utilities scattered across the
8
+ * fleet (canvas Link-header parsing, IC/signupgenius cookie jars, zola JWT
9
+ * decode).
10
+ *
11
+ * Security posture (design §"Bearer-token / error-message leakage"):
12
+ * - {@link formatApiError} runs every upstream body through the shared
13
+ * {@link truncateErrorMessage} (redaction THEN truncation) so bearer tokens
14
+ * and JWTs never reach a tool result, even when an upstream echoes the
15
+ * request back.
16
+ * - {@link createApiClient} never embeds the token in a thrown message; a 401
17
+ * yields a fixed "unauthorized" string, not the credential.
18
+ */
19
+ import { truncateErrorMessage } from '../errors/index.js';
20
+ const DEFAULT_RETRY = { count: 1, delayMs: 2000 };
21
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
22
+ /** Thrown for an upstream 401. Carries the status so callers can trigger a re-auth. */
23
+ export class UnauthorizedError extends Error {
24
+ status = 401;
25
+ constructor(service) {
26
+ super(`Unauthorized (401) from ${service} — the token is missing, invalid, or expired.`);
27
+ this.name = 'UnauthorizedError';
28
+ Object.setPrototypeOf(this, new.target.prototype);
29
+ }
30
+ }
31
+ /** Thrown when a 429 persists after the retry budget is exhausted. */
32
+ export class RateLimitedError extends Error {
33
+ status = 429;
34
+ constructor(service) {
35
+ super(`Rate limited (429) by ${service} after retries.`);
36
+ this.name = 'RateLimitedError';
37
+ Object.setPrototypeOf(this, new.target.prototype);
38
+ }
39
+ }
40
+ function hostOf(baseUrl) {
41
+ try {
42
+ return new URL(baseUrl).host;
43
+ }
44
+ catch {
45
+ return baseUrl;
46
+ }
47
+ }
48
+ /**
49
+ * Build a bearer-auth fetch client with one-shot 429 retry, 401 mapping, 204 /
50
+ * empty-body handling, and redacted error formatting.
51
+ *
52
+ * Consolidates the structurally-identical `client.ts#doRequest` across
53
+ * splitwise/tempo/ioffice/app-store-connect/zola. The retry/401/429 behavior is
54
+ * the hardened superset: 401 → {@link UnauthorizedError} (never echoing the
55
+ * token), 429 → sleep(`delayMs`) and replay up to `count` times then
56
+ * {@link RateLimitedError}, 204/empty → `undefined`, other non-2xx →
57
+ * {@link formatApiError}.
58
+ */
59
+ export function createApiClient(opts) {
60
+ const base = opts.baseUrl.replace(/\/+$/, '');
61
+ const retry = opts.retry ?? DEFAULT_RETRY;
62
+ const service = opts.serviceName ?? hostOf(opts.baseUrl);
63
+ const doFetch = opts.fetchImpl ?? fetch;
64
+ const sleep = opts.sleep ?? defaultSleep;
65
+ async function send(method, path, opt) {
66
+ const token = await opts.getToken();
67
+ const headers = {
68
+ Accept: 'application/json',
69
+ ...(opt.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
70
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
71
+ ...opt.headers,
72
+ };
73
+ const query = opt.query ? buildQueryString(opt.query) : '';
74
+ const url = `${base}${path}${query}`;
75
+ let attempt = 0;
76
+ // attempt 0 is the initial request; up to `retry.count` further attempts on 429.
77
+ for (;;) {
78
+ const res = await doFetch(url, {
79
+ method,
80
+ headers,
81
+ ...(opt.body !== undefined ? { body: JSON.stringify(opt.body) } : {}),
82
+ });
83
+ if (res.status === 429 && attempt < retry.count) {
84
+ attempt += 1;
85
+ await sleep(retry.delayMs);
86
+ continue;
87
+ }
88
+ return res;
89
+ }
90
+ }
91
+ async function fetchJson(method, path, opt = {}) {
92
+ const res = await send(method, path, opt);
93
+ if (res.status === 401)
94
+ throw new UnauthorizedError(service);
95
+ if (res.status === 429)
96
+ throw new RateLimitedError(service);
97
+ if (res.status === 204)
98
+ return undefined;
99
+ const text = await res.text();
100
+ if (!res.ok) {
101
+ throw new Error(formatApiError(res.status, method, path, text, { service }));
102
+ }
103
+ if (text.length === 0)
104
+ return undefined;
105
+ return JSON.parse(text);
106
+ }
107
+ async function fetchHtml(method, path, opt = {}) {
108
+ const headers = { Accept: 'text/html,*/*', ...opt.headers };
109
+ const res = await send(method, path, { ...opt, headers });
110
+ if (res.status === 401)
111
+ throw new UnauthorizedError(service);
112
+ if (res.status === 429)
113
+ throw new RateLimitedError(service);
114
+ const text = await res.text();
115
+ if (!res.ok) {
116
+ throw new Error(formatApiError(res.status, method, path, text, { service }));
117
+ }
118
+ return text;
119
+ }
120
+ return { fetchJson, fetchHtml };
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // buildQueryString
124
+ // ---------------------------------------------------------------------------
125
+ /**
126
+ * Build a URL query string from a params object, returning `''` or
127
+ * `?k=v&k2=v2`. Skips `undefined`, `null`, and empty-string values; expands
128
+ * arrays into repeated keys (skipping null/undefined/empty array members); and
129
+ * percent-encodes keys and values.
130
+ *
131
+ * Consolidates the divergent `buildQueryString` / inline `URLSearchParams`
132
+ * variants across compass/redfin/zillow/homes/opentable/tempo/ioffice into one
133
+ * superset (array support + empty-string skipping + encoding).
134
+ */
135
+ export function buildQueryString(params) {
136
+ const parts = [];
137
+ for (const [key, value] of Object.entries(params)) {
138
+ if (value === undefined || value === null || value === '')
139
+ continue;
140
+ if (Array.isArray(value)) {
141
+ for (const item of value) {
142
+ if (item === undefined || item === null || item === '')
143
+ continue;
144
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(item))}`);
145
+ }
146
+ }
147
+ else {
148
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
149
+ }
150
+ }
151
+ return parts.length > 0 ? `?${parts.join('&')}` : '';
152
+ }
153
+ // ---------------------------------------------------------------------------
154
+ // buildOptionalBody
155
+ // ---------------------------------------------------------------------------
156
+ /**
157
+ * Build a request body from `args`, including only the `optionalFields` that are
158
+ * actually present (not `undefined`). `null` is preserved — some APIs use it to
159
+ * clear a field — only `undefined` (i.e. "not provided") is dropped.
160
+ *
161
+ * Consolidates tempo's optional-field body builder: lets a tool forward a wide
162
+ * args object and emit a minimal PATCH/POST body.
163
+ */
164
+ export function buildOptionalBody(args, optionalFields) {
165
+ const body = {};
166
+ for (const field of optionalFields) {
167
+ if (args[field] !== undefined) {
168
+ body[field] = args[field];
169
+ }
170
+ }
171
+ return body;
172
+ }
173
+ /**
174
+ * Format a non-2xx upstream response into a single, client-safe error string:
175
+ * `"{service} error {status} for {METHOD} {path}: {body}"`.
176
+ *
177
+ * SECURITY: the upstream `errorText` is run through the shared
178
+ * {@link truncateErrorMessage} (redaction of `Bearer <token>` / JWTs FIRST,
179
+ * then truncation) so a raw body that echoes the request — or an upstream that
180
+ * leaks a token — never reaches the caller. The method is upper-cased; an
181
+ * empty/whitespace body is dropped entirely rather than printing a dangling
182
+ * colon.
183
+ */
184
+ export function formatApiError(status, method, path, errorText, opts = {}) {
185
+ const service = opts.service ?? 'API';
186
+ const head = `${service} error ${status} for ${method.toUpperCase()} ${path}`;
187
+ const safe = truncateErrorMessage(errorText ?? '', opts.max).trim();
188
+ return safe.length > 0 ? `${head}: ${safe}` : head;
189
+ }
190
+ /**
191
+ * Parse an RFC 5988 `Link` header into a `{ rel: url }` map. Malformed entries
192
+ * are skipped; `rel="next"` and bare `rel=next` are both accepted. A missing or
193
+ * empty header yields `{}`.
194
+ *
195
+ * Consolidates canvas's pagination Link parser.
196
+ */
197
+ export function parseLinkHeader(header) {
198
+ const out = {};
199
+ if (!header)
200
+ return out;
201
+ for (const part of header.split(',')) {
202
+ const m = part.trim().match(/^<([^>]+)>\s*;\s*rel="?([^";]+)"?/);
203
+ if (m && m[1] && m[2])
204
+ out[m[2].trim()] = m[1];
205
+ }
206
+ return out;
207
+ }
208
+ const MAX_AGE_ZERO_RE = /(?:^|;)\s*Max-Age\s*=\s*0\s*(?:;|$)/i;
209
+ const EXPIRES_EPOCH_RE = /(?:^|;)\s*Expires\s*=\s*Thu,\s*01\s*Jan\s*1970/i;
210
+ /**
211
+ * Parse `Set-Cookie` headers into a deduplicated {@link CookieJar}.
212
+ *
213
+ * Login responses commonly send deletion markers (`Max-Age=0` or an epoch
214
+ * `Expires`) alongside real cookies; forwarding both the delete and set form of
215
+ * a name makes some upstreams (e.g. IC) reject the request. This parser:
216
+ * - drops deletion markers (`Max-Age=0`, epoch `Expires`),
217
+ * - drops empty-value cookies (clearing instructions),
218
+ * - deduplicates by name with **last value wins**, preserving order.
219
+ *
220
+ * Accepts the array from `Headers.getSetCookie()` (or a single joined string —
221
+ * which it splits defensively, though `getSetCookie()` is strongly preferred
222
+ * since commas inside `Expires` make string-splitting lossy).
223
+ *
224
+ * Consolidates the IC/signupgenius cookie-jar logic.
225
+ */
226
+ export function parseCookieJar(setCookieHeaders) {
227
+ const entries = setCookieHeaders == null
228
+ ? []
229
+ : Array.isArray(setCookieHeaders)
230
+ ? setCookieHeaders
231
+ : splitSetCookie(setCookieHeaders);
232
+ const jar = new Map();
233
+ for (const entry of entries) {
234
+ if (MAX_AGE_ZERO_RE.test(entry) || EXPIRES_EPOCH_RE.test(entry))
235
+ continue;
236
+ const nameValue = (entry.split(';')[0] ?? '').trim();
237
+ const eqIdx = nameValue.indexOf('=');
238
+ if (eqIdx < 1)
239
+ continue;
240
+ const name = nameValue.slice(0, eqIdx).trim();
241
+ const value = nameValue.slice(eqIdx + 1).trim();
242
+ if (!value)
243
+ continue;
244
+ jar.set(name, value);
245
+ }
246
+ const cookies = {};
247
+ for (const [k, v] of jar)
248
+ cookies[k] = v;
249
+ const cookieHeader = [...jar.entries()].map(([k, v]) => `${k}=${v}`).join('; ');
250
+ return { cookies, cookieHeader };
251
+ }
252
+ /** Best-effort split of a single joined `set-cookie` string (no `getSetCookie()`). */
253
+ function splitSetCookie(header) {
254
+ // Split on commas that are NOT part of an Expires date ("Thu, 01 Jan ...").
255
+ return header
256
+ .split(/,(?=\s*[^;,\s]+\s*=)/)
257
+ .map((s) => s.trim())
258
+ .filter((s) => s.length > 0);
259
+ }
260
+ // ---------------------------------------------------------------------------
261
+ // JWT helpers
262
+ // ---------------------------------------------------------------------------
263
+ /** Decode the base64url JWT payload to an object, or `null` if it can't be parsed. */
264
+ function decodeJwtPayload(token) {
265
+ if (typeof token !== 'string')
266
+ return null;
267
+ const parts = token.split('.');
268
+ if (parts.length < 2 || !parts[1])
269
+ return null;
270
+ try {
271
+ const json = Buffer.from(parts[1], 'base64url').toString('utf8');
272
+ const payload = JSON.parse(json);
273
+ if (payload === null || typeof payload !== 'object')
274
+ return null;
275
+ return payload;
276
+ }
277
+ catch {
278
+ return null;
279
+ }
280
+ }
281
+ /**
282
+ * Decode a JWT's `exp` claim (seconds since epoch). Throws when the structure is
283
+ * invalid or `exp` is missing/non-numeric — the strict variant zola uses for the
284
+ * token it depends on. For a lenient probe, use {@link validateJwtExpiry}.
285
+ */
286
+ export function decodeJwtExp(token) {
287
+ const payload = decodeJwtPayload(token);
288
+ if (!payload) {
289
+ throw new Error('Invalid JWT: could not decode payload (expected a 3-part base64url token)');
290
+ }
291
+ const exp = payload['exp'];
292
+ if (typeof exp !== 'number') {
293
+ throw new Error('Invalid JWT: missing numeric "exp" claim');
294
+ }
295
+ return exp;
296
+ }
297
+ /**
298
+ * Best-effort extraction of a session id from a JWT payload, checking
299
+ * `session_id` then `sid`. Returns `null` for an undecodable token or absent
300
+ * claim (never throws) — the lenient variant zola uses for the WAF session
301
+ * header.
302
+ */
303
+ export function decodeJwtSessionId(token) {
304
+ const payload = decodeJwtPayload(token);
305
+ if (!payload)
306
+ return null;
307
+ const sid = payload['session_id'] ?? payload['sid'];
308
+ return typeof sid === 'string' ? sid : null;
309
+ }
310
+ /** Tokens within this many seconds of `exp` are flagged as near-expiry. */
311
+ const NEAR_EXPIRY_SKEW_SEC = 300;
312
+ /**
313
+ * Non-throwing expiry probe for a bearer JWT. Returns `{ expired, expiresIn?,
314
+ * warning? }`. An undecodable token (or one lacking a numeric `exp`) is treated
315
+ * as `expired: true` with a warning — failing closed so a malformed token forces
316
+ * a refresh rather than being sent and bouncing as a 401.
317
+ *
318
+ * Near-expiry (within {@link NEAR_EXPIRY_SKEW_SEC}) yields a warning while still
319
+ * reporting `expired: false`, so callers can refresh proactively.
320
+ */
321
+ export function validateJwtExpiry(token, nowMs = Date.now()) {
322
+ const payload = decodeJwtPayload(token);
323
+ const exp = payload?.['exp'];
324
+ if (typeof exp !== 'number') {
325
+ return { expired: true, warning: 'Token could not be decoded or has no expiry; treat as expired.' };
326
+ }
327
+ const nowSec = Math.floor(nowMs / 1000);
328
+ const expiresIn = exp - nowSec;
329
+ if (expiresIn <= 0) {
330
+ return { expired: true, expiresIn, warning: 'Token has expired; refresh before use.' };
331
+ }
332
+ if (expiresIn <= NEAR_EXPIRY_SKEW_SEC) {
333
+ return {
334
+ expired: false,
335
+ expiresIn,
336
+ warning: `Token expires in ${expiresIn}s; refresh soon.`,
337
+ };
338
+ }
339
+ return { expired: false, expiresIn };
340
+ }
341
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `@chrischall/mcp-utils` — core barrel.
3
+ *
4
+ * Re-exports the framework-agnostic building blocks every server in the fleet
5
+ * reaches for: server bootstrap, tool-result formatting, helpful errors,
6
+ * hardened env/config, a bearer API-client kit, zod atoms, and the auth
7
+ * resolver skeletons.
8
+ *
9
+ * Heavier / optional-dependency modules are published as subpath entries and
10
+ * are intentionally NOT re-exported here:
11
+ * - `@chrischall/mcp-utils/session` session registries + token manager
12
+ * - `@chrischall/mcp-utils/fetchproxy` fetchproxy transport adapter
13
+ * - `@chrischall/mcp-utils/html` opt-in HTML scraping helpers
14
+ * - `@chrischall/mcp-utils/test` in-memory test harness
15
+ */
16
+ export * from './server/index.js';
17
+ export * from './response/index.js';
18
+ export * from './errors/index.js';
19
+ export * from './config/index.js';
20
+ export * from './http/index.js';
21
+ export * from './zod/index.js';
22
+ export * from './auth/index.js';
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `@chrischall/mcp-utils` — core barrel.
3
+ *
4
+ * Re-exports the framework-agnostic building blocks every server in the fleet
5
+ * reaches for: server bootstrap, tool-result formatting, helpful errors,
6
+ * hardened env/config, a bearer API-client kit, zod atoms, and the auth
7
+ * resolver skeletons.
8
+ *
9
+ * Heavier / optional-dependency modules are published as subpath entries and
10
+ * are intentionally NOT re-exported here:
11
+ * - `@chrischall/mcp-utils/session` session registries + token manager
12
+ * - `@chrischall/mcp-utils/fetchproxy` fetchproxy transport adapter
13
+ * - `@chrischall/mcp-utils/html` opt-in HTML scraping helpers
14
+ * - `@chrischall/mcp-utils/test` in-memory test harness
15
+ */
16
+ export * from './server/index.js';
17
+ export * from './response/index.js';
18
+ export * from './errors/index.js';
19
+ export * from './config/index.js';
20
+ export * from './http/index.js';
21
+ export * from './zod/index.js';
22
+ export * from './auth/index.js';
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ /**
3
+ * Wrap any JSON-serialisable value as an MCP tool result. This is the single
4
+ * most duplicated snippet across the fleet:
5
+ * `{ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }`
6
+ */
7
+ export declare function textResult(data: unknown): CallToolResult;
8
+ /** Alias for {@link textResult} — pretty-printed JSON tool result. */
9
+ export declare const jsonResult: typeof textResult;
10
+ /** Return a raw string as a text tool result (no JSON stringify). */
11
+ export declare function rawTextResult(text: string): CallToolResult;
12
+ /** Return a base64 image as an MCP image tool result. */
13
+ export declare function imageResult(base64: string, mimeType: string): CallToolResult;
14
+ /** Return an error tool result (`isError: true`) carrying a message. */
15
+ export declare function errorResult(message: string): CallToolResult;
16
+ /**
17
+ * Collapse a JSON:API-shaped payload (`{ data: { id, type, attributes } }` or an
18
+ * array thereof) into plain objects with `id`/`type` merged into attributes.
19
+ * Used by skylight-mcp; opt-in (callers pass payloads they know are JSON:API).
20
+ */
21
+ export declare function flattenJsonApi(payload: unknown): unknown;
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/response/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAIxD;AAED,sEAAsE;AACtE,eAAO,MAAM,UAAU,mBAAa,CAAC;AAErC,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAE1D;AAED,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,CAI5E;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAK3D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAoBxD"}