UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

94 lines (93 loc) 3.01 kB
"use strict"; /** * Cloudflare KV Resume Token Store * * Durable resume-token store backed by a Cloudflare Workers KV namespace, so a * resume token minted at consent time survives cold starts, process restarts, * and multi-instance deployments (the failure mode the in-memory store causes — * the user gets re-prompted because the token vanished). Mirrors the * delegation-verifier-kv.ts adapter shape. * * Key structure: * - `resume:{token}` — JSON-encoded {@link StoredResumeToken} * * Tokens are minted with CSPRNG entropy (see generateResumeToken in * @kya-os/mcp-i-core), never time+Math.random. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.CloudflareKVResumeTokenStore = void 0; const mcp_i_core_1 = require("@kya-os/mcp-i-core"); // Cloudflare KV requires expirationTtl >= 60 seconds. const MIN_KV_TTL_SECONDS = 60; function toTtlSeconds(ms) { return Math.max(MIN_KV_TTL_SECONDS, Math.ceil(ms / 1000)); } class CloudflareKVResumeTokenStore { kv; ttlMs; constructor(kv, ttlMs = 600_000) { this.kv = kv; this.ttlMs = ttlMs; } key(token) { return `resume:${token}`; } async read(token) { const raw = await this.kv.get(this.key(token)); if (!raw) return null; try { return JSON.parse(raw); } catch { return null; } } async create(agentDid, scopes, metadata) { const token = (0, mcp_i_core_1.generateResumeToken)("rt"); const now = Date.now(); const record = { agentDid, scopes, createdAt: now, expiresAt: now + this.ttlMs, metadata, fulfilled: false, }; await this.kv.put(this.key(token), JSON.stringify(record), { expirationTtl: toTtlSeconds(this.ttlMs), }); return token; } async get(token) { const record = await this.read(token); if (!record) return null; if (Date.now() > record.expiresAt) { await this.kv.delete(this.key(token)); return null; } if (record.fulfilled) return null; return { agentDid: record.agentDid, scopes: record.scopes, createdAt: record.createdAt, expiresAt: record.expiresAt, metadata: record.metadata, }; } async fulfill(token) { const record = await this.read(token); if (!record) return; record.fulfilled = true; // Preserve the original expiry window so a fulfilled token can't outlive // its TTL; floor at the KV minimum. const remainingMs = Math.max(0, record.expiresAt - Date.now()); await this.kv.put(this.key(token), JSON.stringify(record), { expirationTtl: toTtlSeconds(remainingMs), }); } } exports.CloudflareKVResumeTokenStore = CloudflareKVResumeTokenStore;