import { ensureSchema, getRawDb } from "./mvp-db"; export type McpExportKind = "recovery" | "resources"; type ExportTokenRow = { kind: McpExportKind; payload: string; expires_at: string; }; function sqliteTimestamp(date: Date) { return date.toISOString().replace("T", " ").slice(0, 19); } async function digest(value: string) { const bytes = await crypto.subtle.digest( "SHA-256", new TextEncoder().encode(value), ); return [...new Uint8Array(bytes)] .map((byte) => byte.toString(16).padStart(2, "0")) .join(""); } export async function issueMcpExportToken( kind: McpExportKind, payload: Record, ttlMinutes = 15, ) { await ensureSchema(); const db = getRawDb(); const token = `${crypto.randomUUID().replaceAll("-", "")}${crypto .randomUUID() .replaceAll("-", "")}`; const expiresAt = new Date( Date.now() + Math.max(1, Math.min(60, ttlMinutes)) * 60_000, ); await db.prepare("DELETE FROM mcp_export_tokens WHERE expires_at <= CURRENT_TIMESTAMP").run(); await db .prepare( `INSERT INTO mcp_export_tokens (token_hash, kind, payload, expires_at) VALUES (?, ?, ?, ?)`, ) .bind(await digest(token), kind, JSON.stringify(payload), sqliteTimestamp(expiresAt)) .run(); return { token, expiresAt: expiresAt.toISOString() }; } export async function consumeMcpExportToken( token: string, expectedKind: McpExportKind, ) { if (!token || token.length < 32) return null; await ensureSchema(); const db = getRawDb(); const tokenHash = await digest(token); const row = await db .prepare( `SELECT kind, payload, expires_at FROM mcp_export_tokens WHERE token_hash = ? AND expires_at > CURRENT_TIMESTAMP`, ) .bind(tokenHash) .first(); if (!row || row.kind !== expectedKind) return null; await db .prepare("DELETE FROM mcp_export_tokens WHERE token_hash = ?") .bind(tokenHash) .run(); try { return JSON.parse(row.payload) as Record; } catch { return null; } }