@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,404 @@
1
+ /**
2
+ * Session scaffolding for the MCP fleet — three related-but-distinct surfaces
3
+ * consolidated behind one subpath (`@chrischall/mcp-utils/session`):
4
+ *
5
+ * 1. {@link SessionRegistry} — an *ephemeral, in-memory* registry of signed-in
6
+ * sessions keyed by account identity, plus {@link registerSessionTools} for
7
+ * the structurally-identical `*_register_session` / `*_set_active_session` /
8
+ * `*_get_session_context` MCP tool trio. Used by the realty MCPs
9
+ * (zillow/redfin/compass/homes/onehome).
10
+ *
11
+ * 2. {@link SessionStore} — a *disk-persisted* store with hardened file perms
12
+ * (0600 file / 0700 dir), normalized keys, and a most-recently-used "active"
13
+ * pointer. Used by ofw/creditkarma/honeybook.
14
+ *
15
+ * 3. {@link TokenManager} — a bearer-token lifecycle manager: proactive refresh
16
+ * inside a 5-minute skew window, reactive 401-replay, and a single-flight
17
+ * semaphore so concurrent callers coalesce into ONE refresh. Used by
18
+ * skylight/canvas/creditkarma/honeybook/zola.
19
+ *
20
+ * Security-sensitive by design (file perms + token-refresh races), so this is
21
+ * the one audited implementation the fleet shares.
22
+ */
23
+ import { z } from 'zod';
24
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, } from 'node:fs';
25
+ import { dirname } from 'node:path';
26
+ import { randomBytes } from 'node:crypto';
27
+ import { textResult } from '../response/index.js';
28
+ /** Generate a short, collision-resistant label id. */
29
+ function makeSessionId() {
30
+ return Date.now().toString(36) + randomBytes(6).toString('hex');
31
+ }
32
+ /**
33
+ * Per-process, in-memory registry of signed-in sessions. The constructor takes
34
+ * no arguments, so it's safe to instantiate per test. Sessions are keyed by
35
+ * `session_id` but de-duplicated by `account_identity`.
36
+ */
37
+ export class SessionRegistry {
38
+ sessions = new Map();
39
+ activeId = null;
40
+ /**
41
+ * Register a new session, or refresh the existing one keyed by
42
+ * `account_identity`. The first session registered becomes the active one.
43
+ */
44
+ register(args) {
45
+ const identity = args.account_identity.trim();
46
+ if (identity.length === 0) {
47
+ throw new Error('register: account_identity must be non-empty.');
48
+ }
49
+ for (const existing of this.sessions.values()) {
50
+ if (existing.account_identity === identity) {
51
+ if (args.auth_mode !== undefined)
52
+ existing.auth_mode = args.auth_mode;
53
+ existing.auth_ready = true;
54
+ existing.registered_at = new Date().toISOString();
55
+ if (args.auth_expires_at !== undefined) {
56
+ existing.auth_expires_at = args.auth_expires_at;
57
+ }
58
+ return { ...existing };
59
+ }
60
+ }
61
+ const sess = {
62
+ session_id: makeSessionId(),
63
+ account_identity: identity,
64
+ auth_mode: args.auth_mode ?? 'browser_session',
65
+ auth_ready: true,
66
+ registered_at: new Date().toISOString(),
67
+ auth_expires_at: args.auth_expires_at ?? null,
68
+ };
69
+ this.sessions.set(sess.session_id, sess);
70
+ if (this.activeId === null)
71
+ this.activeId = sess.session_id;
72
+ return { ...sess };
73
+ }
74
+ /** Switch the active session. Returns `false` if the id is unknown. */
75
+ setActive(sessionId) {
76
+ if (!this.sessions.has(sessionId))
77
+ return false;
78
+ this.activeId = sessionId;
79
+ return true;
80
+ }
81
+ /** Look up a session by id (returns a copy, or `null`). */
82
+ get(sessionId) {
83
+ const s = this.sessions.get(sessionId);
84
+ return s ? { ...s } : null;
85
+ }
86
+ /** Snapshot of the full registry plus the active id. */
87
+ getContext() {
88
+ return {
89
+ active_session_id: this.activeId,
90
+ sessions: Array.from(this.sessions.values()).map((s) => ({ ...s })),
91
+ };
92
+ }
93
+ /** The active session id, if any. */
94
+ activeSessionId() {
95
+ return this.activeId;
96
+ }
97
+ /** Number of registered sessions. */
98
+ size() {
99
+ return this.sessions.size;
100
+ }
101
+ /**
102
+ * Resolve which session a tool call should route through:
103
+ * - `requested` set and known → it;
104
+ * - `requested` set but unknown → throws;
105
+ * - `requested` undefined → the active session;
106
+ * - no sessions registered → `null` (caller uses the default transport).
107
+ */
108
+ resolve(requested) {
109
+ if (requested !== undefined) {
110
+ if (!this.sessions.has(requested)) {
111
+ throw new Error(`Unknown session_id "${requested}". Call the get_session_context tool to see registered sessions.`);
112
+ }
113
+ return requested;
114
+ }
115
+ return this.activeId;
116
+ }
117
+ /** Clear all state. Test helper. */
118
+ reset() {
119
+ this.sessions.clear();
120
+ this.activeId = null;
121
+ }
122
+ }
123
+ /** Construct a fresh in-memory {@link SessionRegistry}. */
124
+ export function createSessionRegistry() {
125
+ return new SessionRegistry();
126
+ }
127
+ /**
128
+ * Register the structurally-identical session tool trio against `server`,
129
+ * backed by `registry`. Replaces every MCP's hand-rolled `src/tools/sessions.ts`.
130
+ */
131
+ export function registerSessionTools(server, registry, opts) {
132
+ const { prefix } = opts;
133
+ const label = opts.serviceLabel ?? prefix;
134
+ const ctxTool = `${prefix}_get_session_context`;
135
+ server.registerTool(`${prefix}_register_session`, {
136
+ title: `Register a signed-in ${label} session`,
137
+ description: `Register (or refresh) an authenticated ${label} session keyed by signed-in account identity. ` +
138
+ 'Re-registering the same `account_identity` updates the existing session rather than creating a duplicate. ' +
139
+ 'Returns the `session_id` to use when routing per-tool calls. ' +
140
+ 'The first registered session becomes the default `active_session_id`.',
141
+ annotations: {
142
+ title: `Register a signed-in ${label} session`,
143
+ readOnlyHint: false,
144
+ idempotentHint: true,
145
+ openWorldHint: false,
146
+ },
147
+ inputSchema: {
148
+ account_identity: z
149
+ .string()
150
+ .min(1)
151
+ .describe('Caller-supplied identifier for the signed-in account (typically the saved-account email).'),
152
+ auth_expires_at: z
153
+ .string()
154
+ .optional()
155
+ .describe('Optional ISO timestamp at which the session expires.'),
156
+ },
157
+ }, async ({ account_identity, auth_expires_at }) => {
158
+ // Pass `auth_expires_at` through as-is: `undefined` means "keep existing",
159
+ // never coerce with `?? null` (that would wipe a prior expiry).
160
+ const session = registry.register({ account_identity, auth_expires_at });
161
+ return textResult({ session, active_session_id: registry.activeSessionId() });
162
+ });
163
+ server.registerTool(`${prefix}_set_active_session`, {
164
+ title: `Set the active ${label} session`,
165
+ description: 'Switch which registered session subsequent tool calls route through by default. ' +
166
+ `Pass a \`session_id\` previously returned by \`${prefix}_register_session\`. ` +
167
+ 'Tools that accept an explicit `session_id` parameter override this default per-call.',
168
+ annotations: {
169
+ title: `Set the active ${label} session`,
170
+ readOnlyHint: false,
171
+ idempotentHint: true,
172
+ openWorldHint: false,
173
+ },
174
+ inputSchema: {
175
+ session_id: z.string().min(1).describe('Session id to make active.'),
176
+ },
177
+ }, async ({ session_id }) => {
178
+ if (!registry.setActive(session_id)) {
179
+ throw new Error(`Unknown session_id "${session_id}". Call ${ctxTool} to see registered sessions.`);
180
+ }
181
+ return textResult({
182
+ active_session_id: registry.activeSessionId(),
183
+ context: registry.getContext(),
184
+ });
185
+ });
186
+ server.registerTool(ctxTool, {
187
+ title: `List all registered ${label} sessions`,
188
+ description: 'Return the full set of registered sessions plus the current `active_session_id`. ' +
189
+ 'When no sessions are registered, `sessions` is empty and `active_session_id` is null.',
190
+ annotations: {
191
+ title: `List all registered ${label} sessions`,
192
+ readOnlyHint: true,
193
+ idempotentHint: true,
194
+ openWorldHint: false,
195
+ },
196
+ inputSchema: {},
197
+ }, async () => textResult(registry.getContext()));
198
+ }
199
+ // ===========================================================================
200
+ // 2. Disk-persisted SessionStore
201
+ // ===========================================================================
202
+ /**
203
+ * Given a full URL or just an origin, return the origin without a trailing
204
+ * slash. Falls back to trimming a trailing slash for non-URL input.
205
+ */
206
+ export function normalizeOrigin(input) {
207
+ try {
208
+ return new URL(input).origin.replace(/\/$/, '');
209
+ }
210
+ catch {
211
+ return input.replace(/\/$/, '');
212
+ }
213
+ }
214
+ /**
215
+ * Disk-persisted session store with hardened file permissions. Records are kept
216
+ * in a `Map` keyed by their normalized key, persisted as a JSON array (insertion
217
+ * order). The file is written with mode `0600` and its directory `0700` so other
218
+ * users on the machine cannot read captured credentials.
219
+ *
220
+ * `add` marks the record most-recently-used; {@link SessionStore.getActiveSession}
221
+ * (and `get()` with no argument) returns it, so a tool call that omits an explicit
222
+ * key picks up the latest session automatically.
223
+ */
224
+ export class SessionStore {
225
+ sessions = new Map();
226
+ mostRecentKey = null;
227
+ filePath;
228
+ keyOf;
229
+ normalizeKey;
230
+ constructor(opts) {
231
+ this.filePath = opts.filePath;
232
+ this.keyOf = opts.keyOf;
233
+ this.normalizeKey = opts.normalizeKey ?? normalizeOrigin;
234
+ this.loadFromDisk();
235
+ }
236
+ loadFromDisk() {
237
+ if (!existsSync(this.filePath))
238
+ return;
239
+ try {
240
+ this.sessions = this.deserialize(readFileSync(this.filePath, 'utf8'));
241
+ const keys = Array.from(this.sessions.keys());
242
+ this.mostRecentKey = keys[keys.length - 1] ?? null;
243
+ }
244
+ catch {
245
+ this.sessions = new Map();
246
+ this.mostRecentKey = null;
247
+ }
248
+ }
249
+ /** Serialize the store to its on-disk JSON form (array, insertion order). */
250
+ serialize() {
251
+ return JSON.stringify(Array.from(this.sessions.values()), null, 2);
252
+ }
253
+ /** Parse on-disk JSON back into a keyed `Map`; empty map on invalid input. */
254
+ deserialize(body) {
255
+ const map = new Map();
256
+ const arr = JSON.parse(body);
257
+ if (!Array.isArray(arr))
258
+ return map;
259
+ for (const raw of arr) {
260
+ if (raw && typeof raw === 'object') {
261
+ const s = raw;
262
+ const key = this.normalizeKey(this.keyOf(s));
263
+ map.set(key, s);
264
+ }
265
+ }
266
+ return map;
267
+ }
268
+ saveToDisk() {
269
+ mkdirSync(dirname(this.filePath), { recursive: true });
270
+ writeFileSync(this.filePath, this.serialize(), { mode: 0o600 });
271
+ // Best-effort tighten existing file + dir (writeFileSync respects umask on
272
+ // pre-existing files, so re-assert 0600/0700 explicitly).
273
+ try {
274
+ chmodSync(this.filePath, 0o600);
275
+ chmodSync(dirname(this.filePath), 0o700);
276
+ }
277
+ catch {
278
+ /* best-effort */
279
+ }
280
+ }
281
+ /** Insert or replace a record, normalizing its key and marking it active. */
282
+ add(session) {
283
+ const key = this.normalizeKey(this.keyOf(session));
284
+ this.sessions.set(key, session);
285
+ this.mostRecentKey = key;
286
+ this.saveToDisk();
287
+ }
288
+ /** Look up by key; with no key, returns the active (most-recent) session. */
289
+ get(key) {
290
+ if (key !== undefined)
291
+ return this.sessions.get(this.normalizeKey(key)) ?? null;
292
+ if (this.mostRecentKey !== null)
293
+ return this.sessions.get(this.mostRecentKey) ?? null;
294
+ return null;
295
+ }
296
+ /** The most-recently-added session, or `null`. */
297
+ getActiveSession() {
298
+ return this.get();
299
+ }
300
+ /** All sessions in insertion order. */
301
+ list() {
302
+ return Array.from(this.sessions.values());
303
+ }
304
+ /** Remove a session; fixes up the active pointer. Returns whether it existed. */
305
+ remove(key) {
306
+ const normalized = this.normalizeKey(key);
307
+ const had = this.sessions.delete(normalized);
308
+ if (had) {
309
+ if (this.mostRecentKey === normalized) {
310
+ const keys = Array.from(this.sessions.keys());
311
+ this.mostRecentKey = keys[keys.length - 1] ?? null;
312
+ }
313
+ this.saveToDisk();
314
+ }
315
+ return had;
316
+ }
317
+ /** Clear in-memory state without touching disk. Test helper. */
318
+ resetForTest() {
319
+ this.sessions.clear();
320
+ this.mostRecentKey = null;
321
+ }
322
+ }
323
+ // ===========================================================================
324
+ // 3. TokenManager — bearer lifecycle (skew, proactive + reactive, race-safe)
325
+ // ===========================================================================
326
+ /** Refresh proactively this many ms before the access token expires. */
327
+ export const TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1000;
328
+ /**
329
+ * Manages a bearer access token's lifecycle:
330
+ *
331
+ * - **Proactive:** {@link TokenManager.getAccessToken} refreshes when the token
332
+ * is within `skewMs` (default 5 min) of expiry, returning a still-valid token.
333
+ * - **Reactive:** {@link TokenManager.withAuth} runs a request, and on a `401`
334
+ * refreshes once and replays exactly once (no infinite loop).
335
+ * - **Race-safe:** concurrent refreshes coalesce onto a single in-flight promise
336
+ * (semaphore), so a burst of callers triggers exactly ONE token exchange. The
337
+ * in-flight promise is cleared on settle so a later refresh can run again.
338
+ */
339
+ export class TokenManager {
340
+ accessToken;
341
+ refreshToken;
342
+ expiresAt;
343
+ refreshFn;
344
+ skewMs;
345
+ inFlight;
346
+ constructor(opts) {
347
+ this.accessToken = opts.initial.accessToken;
348
+ this.refreshToken = opts.initial.refreshToken;
349
+ this.expiresAt = opts.initial.expiresAt;
350
+ this.refreshFn = opts.refresh;
351
+ this.skewMs = opts.skewMs ?? TOKEN_REFRESH_SKEW_MS;
352
+ }
353
+ /** Whether the token is within the skew window of (or past) expiry. */
354
+ needsRefresh() {
355
+ return Date.now() >= this.expiresAt - this.skewMs;
356
+ }
357
+ /**
358
+ * Single-flight refresh. Concurrent callers share one in-flight promise; it is
359
+ * cleared on settle (success or failure) so a subsequent refresh can proceed.
360
+ */
361
+ refreshNow() {
362
+ if (!this.inFlight) {
363
+ const rt = this.refreshToken;
364
+ if (rt === undefined) {
365
+ return Promise.reject(new Error('TokenManager: cannot refresh — no refresh token is available.'));
366
+ }
367
+ this.inFlight = (async () => {
368
+ const tok = await this.refreshFn(rt);
369
+ this.accessToken = tok.accessToken;
370
+ if (tok.refreshToken !== undefined && tok.refreshToken !== '') {
371
+ this.refreshToken = tok.refreshToken;
372
+ }
373
+ this.expiresAt = tok.expiresAt;
374
+ })().finally(() => {
375
+ this.inFlight = undefined;
376
+ });
377
+ }
378
+ return this.inFlight;
379
+ }
380
+ /** Get a valid access token, refreshing proactively inside the skew window. */
381
+ async getAccessToken() {
382
+ if (this.needsRefresh())
383
+ await this.refreshNow();
384
+ return this.accessToken;
385
+ }
386
+ /** Current absolute expiry (epoch ms). */
387
+ getExpiresAt() {
388
+ return this.expiresAt;
389
+ }
390
+ /**
391
+ * Run an authenticated request with reactive 401-replay. `call` receives a
392
+ * valid access token and returns a `Response`. On `401`, the token is
393
+ * refreshed once and `call` is invoked again exactly once.
394
+ */
395
+ async withAuth(call) {
396
+ let res = await call(await this.getAccessToken());
397
+ if (res.status === 401) {
398
+ await this.refreshNow();
399
+ res = await call(this.accessToken);
400
+ }
401
+ return res;
402
+ }
403
+ }
404
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
4
+ import type { Mock } from 'vitest';
5
+ /** A function that registers one or more tools onto a fresh `McpServer`. */
6
+ export type RegisterFn = (server: McpServer) => void | Promise<void>;
7
+ /** The connected test harness returned by {@link createTestHarness}. */
8
+ export interface TestHarness {
9
+ /** The MCP client side of the in-memory transport pair. */
10
+ client: Client;
11
+ /** The MCP server the `registerFn` was applied to. */
12
+ server: McpServer;
13
+ /** Call a registered tool by name; arguments default to `{}`. */
14
+ callTool: (name: string, args?: Record<string, unknown>) => Promise<CallToolResult>;
15
+ /** List the tools the server advertises (name only). */
16
+ listTools: () => Promise<{
17
+ name: string;
18
+ }[]>;
19
+ /** Tear down both ends of the transport. Safe to call more than once. */
20
+ close: () => Promise<void>;
21
+ }
22
+ /**
23
+ * Create a connected `McpServer` + `Client` pair wired over
24
+ * `InMemoryTransport`. The byte-identical helper every MCP's `tests/helpers.ts`
25
+ * defines — register your tools, then drive them through the real client RPC
26
+ * path (schema validation, content envelopes, isError, and all).
27
+ */
28
+ export declare function createTestHarness(registerFn: RegisterFn): Promise<TestHarness>;
29
+ /**
30
+ * Parse the JSON body out of a tool's `CallToolResult`. Fleet tools return a
31
+ * single text block of `JSON.stringify(data, null, 2)`; this is the inverse.
32
+ * Throws a contextual error (rather than a bare `TypeError`/`SyntaxError`) when
33
+ * the result is empty, non-text, or not valid JSON — those are the test
34
+ * failures you actually want to read.
35
+ */
36
+ export declare function parseToolResult<T = unknown>(result: CallToolResult): T;
37
+ /** Options for {@link versionSyncTest}. */
38
+ export interface VersionSyncOptions {
39
+ /** Directory to walk for `.ts` files (typically `<repo>/src`). */
40
+ srcDir: string;
41
+ /** Path to the `package.json` whose `version` is the source of truth. */
42
+ pkgPath: string;
43
+ }
44
+ /**
45
+ * The release-please drift guard. Walks `srcDir` for every line carrying an
46
+ * `x-release-please-version` annotation and asserts the version literal on that
47
+ * line matches `package.json#version`.
48
+ *
49
+ * Why this exists: a recurring footgun where a `VERSION` constant (the MCP's
50
+ * self-reported version + fetchproxy bridge identity) silently drifts from
51
+ * `package.json` because release-please's `extra-files` registration lacks the
52
+ * marker. resy-mcp v0.2.0 and opentable-mcp shipped this bug repeatedly.
53
+ *
54
+ * Returns the list of mismatch descriptions (`file:line → found (expected X)`).
55
+ * An empty array means in sync — callers assert `expect(result).toEqual([])`.
56
+ * Marker lines with no version literal (e.g. a docstring describing the
57
+ * convention) are intentionally skipped, so the test never trips on itself.
58
+ */
59
+ export declare function versionSyncTest({ srcDir, pkgPath }: VersionSyncOptions): string[];
60
+ /**
61
+ * The shape `@fetchproxy/bootstrap`'s `bootstrap()` resolves to: extracted
62
+ * browser state keyed by name. Mirrored here (rather than imported) because the
63
+ * bootstrap package is an optional, browser-side dep that consumers mock at the
64
+ * module boundary — the harness must not pull it.
65
+ */
66
+ export interface BootstrapResult {
67
+ /** Cookie name → value. */
68
+ cookies: Record<string, string>;
69
+ /** `localStorage` key → value. */
70
+ localStorage: Record<string, string>;
71
+ /** `sessionStorage` key → value. */
72
+ sessionStorage: Record<string, string>;
73
+ /** Request headers captured during the bootstrap navigation. */
74
+ capturedHeaders: Record<string, string>;
75
+ }
76
+ /**
77
+ * Build a fully-shaped {@link BootstrapResult}, with empty maps as defaults and
78
+ * `overrides` shallow-merged on top. Keeps tests from re-declaring the four
79
+ * empty maps every time they only care about, say, `cookies`.
80
+ */
81
+ export declare function makeBootstrapResult(overrides?: Partial<BootstrapResult>): BootstrapResult;
82
+ /** Handle returned by {@link mockFetchproxyBootstrap}. */
83
+ export interface FetchproxyBootstrapMock {
84
+ /** The spy standing in for `bootstrap()`. Assert/override per test. */
85
+ bootstrap: Mock<(...args: unknown[]) => Promise<BootstrapResult>>;
86
+ /**
87
+ * A module factory matching `@fetchproxy/bootstrap`'s export surface — pass
88
+ * to `vi.mock('@fetchproxy/bootstrap', mock.module)`.
89
+ */
90
+ module: () => {
91
+ bootstrap: (...args: unknown[]) => Promise<BootstrapResult>;
92
+ };
93
+ /** Clear recorded calls and restore the default resolved value. */
94
+ reset: () => void;
95
+ }
96
+ /**
97
+ * Mock `@fetchproxy/bootstrap` at the module boundary so tests never open a
98
+ * real WebSocket to a browser bridge. Returns a spy that resolves a default
99
+ * {@link BootstrapResult} (overridable per call via `bootstrap.mockResolvedValue`).
100
+ *
101
+ * Usage:
102
+ * ```ts
103
+ * const fp = mockFetchproxyBootstrap({ cookies: { SID: 'x' } });
104
+ * vi.mock('@fetchproxy/bootstrap', fp.module);
105
+ * // ...import the SUT, then assert on fp.bootstrap
106
+ * ```
107
+ * Because `vi.mock` is hoisted, declare the mock handle and the `vi.mock` call
108
+ * before importing the system under test.
109
+ */
110
+ export declare function mockFetchproxyBootstrap(defaultResult?: Partial<BootstrapResult>): FetchproxyBootstrapMock;
111
+ /**
112
+ * Spy on an API client's async request methods and (optionally) stub each one's
113
+ * resolved value. Mirrors the `vi.spyOn(client, 'request').mockResolvedValue(...)`
114
+ * boilerplate every tool-level test repeats.
115
+ *
116
+ * For each entry in `returns`: a spy is installed on `client[method]`. If the
117
+ * value is `undefined`, the spy passes through to the real implementation;
118
+ * otherwise it `mockResolvedValue`s the provided value. Remember to
119
+ * `vi.restoreAllMocks()` in `afterEach`.
120
+ *
121
+ * @returns A map of method name → installed spy, for call assertions.
122
+ */
123
+ export declare function setupClientMocks<C extends object>(client: C, returns: Partial<Record<keyof C & string, unknown>>): Record<string, Mock>;
124
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"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"}