import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import type { Mock } from 'vitest'; /** A function that registers one or more tools onto a fresh `McpServer`. */ export type RegisterFn = (server: McpServer) => void | Promise; /** The connected test harness returned by {@link createTestHarness}. */ export interface TestHarness { /** The MCP client side of the in-memory transport pair. */ client: Client; /** The MCP server the `registerFn` was applied to. */ server: McpServer; /** Call a registered tool by name; arguments default to `{}`. */ callTool: (name: string, args?: Record) => Promise; /** List the tools the server advertises (name only). */ listTools: () => Promise<{ name: string; }[]>; /** Tear down both ends of the transport. Safe to call more than once. */ close: () => Promise; } /** * Create a connected `McpServer` + `Client` pair wired over * `InMemoryTransport`. The byte-identical helper every MCP's `tests/helpers.ts` * defines — register your tools, then drive them through the real client RPC * path (schema validation, content envelopes, isError, and all). * * Applies the same error-hint surfacing `createMcpServer` does, so a tool's * failure text under test is the text production returns. */ export declare function createTestHarness(registerFn: RegisterFn): Promise; /** * Parse the JSON body out of a tool's `CallToolResult`. Fleet tools return a * single text block of `JSON.stringify(data, null, 2)`; this is the inverse. * Throws a contextual error (rather than a bare `TypeError`/`SyntaxError`) when * the result is empty, non-text, or not valid JSON — those are the test * failures you actually want to read. */ export declare function parseToolResult(result: CallToolResult): T; /** Options for {@link versionSyncTest}. */ export interface VersionSyncOptions { /** Directory to walk for `.ts` files (typically `/src`). */ srcDir: string; /** Path to the `package.json` whose `version` is the source of truth. */ pkgPath: string; } /** * The release-please drift guard. Walks `srcDir` for every line carrying an * `x-release-please-version` annotation and asserts the version literal on that * line matches `package.json#version`. * * Why this exists: a recurring footgun where a `VERSION` constant (the MCP's * self-reported version + fetchproxy bridge identity) silently drifts from * `package.json` because release-please's `extra-files` registration lacks the * marker. resy-mcp v0.2.0 and opentable-mcp shipped this bug repeatedly. * * Returns the list of mismatch descriptions (`file:line → found (expected X)`). * An empty array means in sync — callers assert `expect(result).toEqual([])`. * Marker lines with no version literal (e.g. a docstring describing the * convention) are intentionally skipped, so the test never trips on itself. */ export declare function versionSyncTest({ srcDir, pkgPath }: VersionSyncOptions): string[]; /** * The shape `@fetchproxy/bootstrap`'s `bootstrap()` resolves to: extracted * browser state keyed by name. Mirrored here (rather than imported) because the * bootstrap package is an optional, browser-side dep that consumers mock at the * module boundary — the harness must not pull it. */ export interface BootstrapResult { /** Cookie name → value. */ cookies: Record; /** `localStorage` key → value. */ localStorage: Record; /** `sessionStorage` key → value. */ sessionStorage: Record; /** Request headers captured during the bootstrap navigation. */ capturedHeaders: Record; } /** * Build a fully-shaped {@link BootstrapResult}, with empty maps as defaults and * `overrides` shallow-merged on top. Keeps tests from re-declaring the four * empty maps every time they only care about, say, `cookies`. */ export declare function makeBootstrapResult(overrides?: Partial): BootstrapResult; /** Handle returned by {@link mockFetchproxyBootstrap}. */ export interface FetchproxyBootstrapMock { /** The spy standing in for `bootstrap()`. Assert/override per test. */ bootstrap: Mock<(...args: unknown[]) => Promise>; /** * A module factory matching `@fetchproxy/bootstrap`'s export surface — pass * to `vi.mock('@fetchproxy/bootstrap', mock.module)`. */ module: () => { bootstrap: (...args: unknown[]) => Promise; }; /** Clear recorded calls and restore the default resolved value. */ reset: () => void; } /** * Mock `@fetchproxy/bootstrap` at the module boundary so tests never open a * real WebSocket to a browser bridge. Returns a spy that resolves a default * {@link BootstrapResult} (overridable per call via `bootstrap.mockResolvedValue`). * * Usage: * ```ts * const fp = mockFetchproxyBootstrap({ cookies: { SID: 'x' } }); * vi.mock('@fetchproxy/bootstrap', fp.module); * // ...import the SUT, then assert on fp.bootstrap * ``` * Because `vi.mock` is hoisted, declare the mock handle and the `vi.mock` call * before importing the system under test. */ export declare function mockFetchproxyBootstrap(defaultResult?: Partial): FetchproxyBootstrapMock; /** * Spy on an API client's async request methods and (optionally) stub each one's * resolved value. Mirrors the `vi.spyOn(client, 'request').mockResolvedValue(...)` * boilerplate every tool-level test repeats. * * For each entry in `returns`: a spy is installed on `client[method]`. If the * value is `undefined`, the spy passes through to the real implementation; * otherwise it `mockResolvedValue`s the provided value. Remember to * `vi.restoreAllMocks()` in `afterEach`. * * @returns A map of method name → installed spy, for call assertions. */ export declare function setupClientMocks(client: C, returns: Partial>): Record; //# sourceMappingURL=index.d.ts.map