Expand KOC LOOP MCP operations

This commit is contained in:
巫凤萍
2026-08-07 14:12:52 +08:00
parent 1a9a113229
commit 5670a7939b
15 changed files with 2426 additions and 24 deletions

75
lib/mcp-export-token.ts Normal file
View File

@@ -0,0 +1,75 @@
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<string, unknown>,
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<ExportTokenRow>();
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<string, unknown>;
} catch {
return null;
}
}