import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import type { ChannelTranscriptsManifest } from "yt-dlp-transcript-common/lib/manifest"; import type { TranscriptDetail } from "yt-dlp-transcript-common/lib/transcripts"; import type { SearchAlias } from "yt-dlp-transcript-common/lib/searchAliases"; import type { ChannelGroups, ChannelRef, ShardSource, VideoAvailability } from "./source"; import { SourceRegistry } from "./sourceRegistry"; import { createServer } from "./server"; import type { FetchClipDeps, HttpInit } from "./fetchClip"; // TEST KIT for the editor-backed tools (imported only by *.test.ts): a fake // editor that answers by URL and records what it was sent, and a server over // an empty archive reached through tools/call. No network, no timers. type Sent = { url: string; method: string; headers?: Record; body?: unknown }; export function fakeEditor( answer: (s: Sent) => { status: number; body: unknown } | Error, env: Record = { WORKER_TOKEN: "tok", ARCHILYZER_EDITOR_URL: "http://editor.test/" }, ): { deps: Partial & Pick; sent: Sent[] } { const sent: Sent[] = []; return { sent, deps: { env, fetch: async (url: string, init?: HttpInit) => { const s: Sent = { url, method: init?.method ?? "GET", headers: init?.headers, body: init?.body ? JSON.parse(init.body) : undefined, }; sent.push(s); const a = answer(s); if (a instanceof Error) throw a; return { status: a.status, json: async () => a.body }; }, sleep: async () => {}, now: () => 0, }, }; } class EmptySource implements ShardSource { readonly label = "local:/srv/fixture"; async loadAliases(): Promise { return []; } async loadGroups(): Promise { return { groups: [], defaultGroupId: "default" }; } async listChannels(): Promise { return [{ key: "demo", slug: "demo", name: "demo" }]; } async transcriptsManifest(): Promise { return { slugToPage: {} } as unknown as ChannelTranscriptsManifest; } async transcriptPage(): Promise { return []; } publicOrigin(): string | null { return null; } async subsManifest(): Promise { return null; } async subsPage(): Promise<[]> { return []; } async postsManifest(): Promise { return null; } async postsPage(): Promise<[]> { return []; } async availabilityMap(): Promise> { return new Map(); } } export async function connectWith(deps: Partial): Promise { const server = createServer(SourceRegistry.forSource(new EmptySource()), { fetchClipDeps: deps }); const [ct, st] = InMemoryTransport.createLinkedPair(); const client = new Client({ name: "test", version: "0" }, { capabilities: {} }); await Promise.all([server.connect(st), client.connect(ct)]); return client; } export const textOf = (res: unknown) => (res as { content: { type: string; text: string }[] }).content.map((c) => c.text).join("\n"); export const isError = (res: unknown) => (res as { isError?: boolean }).isError === true;