| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { Ratelimit } from '@upstash/ratelimit'; |
| import { Redis } from '@upstash/redis'; |
| |
| import { getClientIp } from '../_rate-limit.js'; |
| |
| import { getPublicCorsHeaders } from '../_cors.js'; |
| |
| import { jsonResponse } from '../_json-response.js'; |
| |
| import { keyFingerprint, sha256Hex, timingSafeIncludes, verifyPkceS256 } from '../_crypto.js'; |
| import { validateProMcpToken } from '../../server/_shared/pro-mcp-token'; |
| import type { ProMcpValidateUnion } from '../../server/_shared/pro-mcp-token'; |
|
|
| export const config = { runtime: 'edge' }; |
|
|
| const TOKEN_TTL_SECONDS = 3600; |
| const REFRESH_TTL_SECONDS = 604800; |
| const CLIENT_TTL_SECONDS = 90 * 24 * 3600; |
|
|
| const NO_STORE = { 'Cache-Control': 'no-store', Pragma: 'no-cache' }; |
|
|
| function jsonResp(body: unknown, status = 200): Response { |
| return jsonResponse(body, status, { ...getPublicCorsHeaders('POST, OPTIONS'), ...NO_STORE }); |
| } |
|
|
| |
| let _rl: Ratelimit | null = null; |
| function getRatelimit(): Ratelimit | null { |
| if (_rl) return _rl; |
| const url = process.env.UPSTASH_REDIS_REST_URL; |
| const token = process.env.UPSTASH_REDIS_REST_TOKEN; |
| if (!url || !token) return null; |
| _rl = new Ratelimit({ |
| redis: new Redis({ url, token }), |
| limiter: Ratelimit.slidingWindow(10, '60 s'), |
| prefix: 'rl:oauth-token', |
| analytics: false, |
| }); |
| return _rl; |
| } |
|
|
| async function validateSecret(secret: string | null | undefined): Promise<boolean> { |
| if (!secret) return false; |
| const validKeys = (process.env.WORLDMONITOR_VALID_KEYS || '').split(',').filter(Boolean); |
| return timingSafeIncludes(secret, validKeys); |
| } |
|
|
| |
| |
| |
| |
|
|
| type PipelineCommand = (string | number | unknown)[]; |
| interface PipelineResult { result?: string; error?: string } |
|
|
| async function rawRedisPipeline(commands: PipelineCommand[]): Promise<PipelineResult[] | null> { |
| const url = process.env.UPSTASH_REDIS_REST_URL; |
| const token = process.env.UPSTASH_REDIS_REST_TOKEN; |
| if (!url || !token) return null; |
| try { |
| const resp = await fetch(`${url}/pipeline`, { |
| method: 'POST', |
| headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, |
| body: JSON.stringify(commands), |
| signal: AbortSignal.timeout(3_000), |
| }); |
| if (!resp.ok) return null; |
| return (await resp.json().catch(() => null)) as PipelineResult[] | null; |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| async function rawRedisGetDel(key: string): Promise<unknown | null> { |
| const url = process.env.UPSTASH_REDIS_REST_URL; |
| const token = process.env.UPSTASH_REDIS_REST_TOKEN; |
| if (!url || !token) throw new Error('Redis not configured'); |
| const resp = await fetch(`${url}/getdel/${encodeURIComponent(key)}`, { |
| headers: { Authorization: `Bearer ${token}` }, |
| signal: AbortSignal.timeout(3_000), |
| }); |
| if (!resp.ok) throw new Error(`Redis HTTP ${resp.status}`); |
| const data = (await resp.json()) as { result?: string | null }; |
| if (!data?.result) return null; |
| try { |
| return JSON.parse(data.result); |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| async function rawRedisGet(key: string): Promise<unknown | null> { |
| const url = process.env.UPSTASH_REDIS_REST_URL; |
| const token = process.env.UPSTASH_REDIS_REST_TOKEN; |
| if (!url || !token) throw new Error('Redis not configured'); |
| const resp = await fetch(`${url}/get/${encodeURIComponent(key)}`, { |
| headers: { Authorization: `Bearer ${token}` }, |
| signal: AbortSignal.timeout(3_000), |
| }); |
| if (!resp.ok) throw new Error(`Redis HTTP ${resp.status}`); |
| const data = (await resp.json()) as { result?: string | null }; |
| if (!data?.result) return null; |
| try { |
| return JSON.parse(data.result); |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| async function storeLegacyToken( |
| pipeline: (commands: PipelineCommand[]) => Promise<PipelineResult[] | null>, |
| uuid: string, |
| apiKey: string, |
| ): Promise<boolean> { |
| const fingerprint = await keyFingerprint(apiKey); |
| const results = await pipeline([ |
| ['SET', `oauth:token:${uuid}`, JSON.stringify(fingerprint), 'EX', TOKEN_TTL_SECONDS], |
| ]); |
| return Array.isArray(results) && results[0]?.result === 'OK'; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function storeNewTokens( |
| pipeline: (commands: PipelineCommand[]) => Promise<PipelineResult[] | null>, |
| accessUuid: string, |
| refreshUuid: string, |
| apiKeyHash: string, |
| clientId: string, |
| scope: string, |
| familyId: string, |
| ): Promise<boolean> { |
| const results = await pipeline([ |
| ['SET', `oauth:token:${accessUuid}`, JSON.stringify(apiKeyHash), 'EX', TOKEN_TTL_SECONDS], |
| ['SET', accessTokenFamilyKey(accessUuid), JSON.stringify(familyId), 'EX', TOKEN_TTL_SECONDS], |
| [ |
| 'SET', |
| `oauth:refresh:${refreshUuid}`, |
| JSON.stringify({ client_id: clientId, api_key_hash: apiKeyHash, scope, family_id: familyId }), |
| 'EX', |
| REFRESH_TTL_SECONDS, |
| ], |
| |
| |
| |
| ['SET', refreshFamilyPointerKey(refreshUuid), JSON.stringify(familyId), 'EX', REFRESH_TTL_SECONDS], |
| ]); |
| return Array.isArray(results) && results.every((r) => r?.result === 'OK'); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function storeProTokens( |
| pipeline: (commands: PipelineCommand[]) => Promise<PipelineResult[] | null>, |
| accessUuid: string, |
| refreshUuid: string, |
| userId: string, |
| mcpTokenId: string, |
| clientId: string, |
| scope: string, |
| familyId: string, |
| ): Promise<boolean> { |
| const results = await pipeline([ |
| [ |
| 'SET', |
| `oauth:token:${accessUuid}`, |
| JSON.stringify({ kind: 'pro', userId, mcpTokenId }), |
| 'EX', |
| TOKEN_TTL_SECONDS, |
| ], |
| ['SET', accessTokenFamilyKey(accessUuid), JSON.stringify(familyId), 'EX', TOKEN_TTL_SECONDS], |
| [ |
| 'SET', |
| `oauth:refresh:${refreshUuid}`, |
| JSON.stringify({ kind: 'pro', client_id: clientId, userId, mcpTokenId, scope, family_id: familyId }), |
| 'EX', |
| REFRESH_TTL_SECONDS, |
| ], |
| |
| ['SET', refreshFamilyPointerKey(refreshUuid), JSON.stringify(familyId), 'EX', REFRESH_TTL_SECONDS], |
| ]); |
| return Array.isArray(results) && results.every((r) => r?.result === 'OK'); |
| } |
|
|
| function accessTokenFamilyKey(accessToken: string): string { |
| return `oauth:tokenfam:${accessToken}`; |
| } |
|
|
| function refreshFamilyPointerKey(refreshToken: string): string { |
| return `oauth:famptr:${refreshToken}`; |
| } |
|
|
| function refreshFamilyRevocationKey(familyId: string): string { |
| return `oauth:famrev:${familyId}`; |
| } |
|
|
| function pipelineOk(results: PipelineResult[] | null): boolean { |
| return Array.isArray(results) && results.every((r) => r?.result === 'OK'); |
| } |
|
|
| async function persistRefreshFamilyPointer( |
| deps: TokenHandlerDeps, |
| refreshToken: string, |
| familyId: string, |
| ): Promise<boolean> { |
| return pipelineOk(await deps.redisPipeline([ |
| ['SET', refreshFamilyPointerKey(refreshToken), JSON.stringify(familyId), 'EX', REFRESH_TTL_SECONDS], |
| ])); |
| } |
|
|
| async function markRefreshFamilyRevoked(deps: TokenHandlerDeps, familyId: string): Promise<boolean> { |
| return pipelineOk(await deps.redisPipeline([ |
| ['SET', refreshFamilyRevocationKey(familyId), '1', 'EX', REFRESH_TTL_SECONDS], |
| ])); |
| } |
|
|
| async function restoreConsumedRefreshToken( |
| deps: TokenHandlerDeps, |
| refreshToken: string, |
| refreshData: RefreshDataPro | RefreshDataLegacy, |
| ): Promise<boolean> { |
| const commands: PipelineCommand[] = [ |
| ['SET', `oauth:refresh:${refreshToken}`, JSON.stringify(refreshData), 'EX', REFRESH_TTL_SECONDS], |
| ]; |
| if (refreshData.family_id) { |
| commands.push([ |
| 'SET', |
| refreshFamilyPointerKey(refreshToken), |
| JSON.stringify(refreshData.family_id), |
| 'EX', |
| REFRESH_TTL_SECONDS, |
| ]); |
| } |
| return pipelineOk(await deps.redisPipeline(commands)); |
| } |
|
|
| |
| |
| |
|
|
| export interface TokenHandlerDeps { |
| |
| redisGetDel: (key: string) => Promise<unknown | null>; |
| |
| redisGet: (key: string) => Promise<unknown | null>; |
| |
| redisPipeline: (commands: PipelineCommand[]) => Promise<PipelineResult[] | null>; |
| |
| |
| |
| |
| |
| |
| |
| validateProMcpToken: typeof validateProMcpToken; |
| |
| randomUuid: () => string; |
| } |
|
|
| interface CodeDataPro { |
| kind: 'pro'; |
| userId: string; |
| mcpTokenId: string; |
| client_id: string; |
| redirect_uri: string; |
| code_challenge: string; |
| scope?: string; |
| } |
|
|
| interface CodeDataLegacy { |
| client_id: string; |
| redirect_uri: string; |
| code_challenge: string; |
| scope?: string; |
| api_key_hash: string; |
| kind?: undefined; |
| } |
|
|
| interface RefreshDataPro { |
| kind: 'pro'; |
| client_id: string; |
| userId: string; |
| mcpTokenId: string; |
| scope: string; |
| family_id: string; |
| } |
|
|
| interface RefreshDataLegacy { |
| client_id: string; |
| api_key_hash: string; |
| scope: string; |
| family_id: string; |
| kind?: undefined; |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| async function handleAuthorizationCode( |
| params: URLSearchParams, |
| clientId: string | null, |
| deps: TokenHandlerDeps, |
| ): Promise<Response> { |
| const code = params.get('code'); |
| const codeVerifier = params.get('code_verifier'); |
| const redirectUri = params.get('redirect_uri'); |
|
|
| if (!code || !codeVerifier || !clientId || !redirectUri) { |
| return jsonResp( |
| { |
| error: 'invalid_request', |
| error_description: 'Missing required parameters: code, code_verifier, client_id, redirect_uri', |
| }, |
| 400, |
| ); |
| } |
|
|
| |
| if ( |
| codeVerifier.length < 43 || |
| codeVerifier.length > 128 || |
| !/^[A-Za-z0-9\-._~]+$/.test(codeVerifier) |
| ) { |
| return jsonResp( |
| { |
| error: 'invalid_request', |
| error_description: 'code_verifier must be 43-128 URL-safe characters [A-Za-z0-9-._~]', |
| }, |
| 400, |
| ); |
| } |
|
|
| |
| let codeData: CodeDataPro | CodeDataLegacy | null; |
| try { |
| codeData = (await deps.redisGetDel(`oauth:code:${code}`)) as CodeDataPro | CodeDataLegacy | null; |
| } catch { |
| return jsonResp( |
| { error: 'server_error', error_description: 'Auth service temporarily unavailable. Please retry.' }, |
| 503, |
| ); |
| } |
| if (!codeData) { |
| return jsonResp( |
| { error: 'invalid_grant', error_description: 'Authorization code is invalid, expired, or already used' }, |
| 400, |
| ); |
| } |
| if (codeData.client_id !== clientId) { |
| return jsonResp({ error: 'invalid_grant', error_description: 'client_id mismatch' }, 400); |
| } |
| if (codeData.redirect_uri !== redirectUri) { |
| return jsonResp({ error: 'invalid_grant', error_description: 'redirect_uri mismatch' }, 400); |
| } |
|
|
| |
| const pkceVerify = await verifyPkceS256(codeVerifier, codeData.code_challenge); |
| if (pkceVerify === null) { |
| return jsonResp({ error: 'invalid_request', error_description: 'Malformed PKCE parameters' }, 400); |
| } |
| if (pkceVerify === false) { |
| return jsonResp( |
| { error: 'invalid_grant', error_description: 'code_verifier does not match code_challenge' }, |
| 400, |
| ); |
| } |
|
|
| const clientCheck = await checkClientExists(deps, clientId); |
| if (clientCheck) return clientCheck; |
|
|
| const accessUuid = deps.randomUuid(); |
| const refreshUuid = deps.randomUuid(); |
| const familyId = deps.randomUuid(); |
|
|
| |
| |
| if (codeData.kind === 'pro') { |
| const scope = codeData.scope ?? 'mcp_pro'; |
| const stored = await storeProTokens( |
| deps.redisPipeline, |
| accessUuid, |
| refreshUuid, |
| codeData.userId, |
| codeData.mcpTokenId, |
| clientId, |
| scope, |
| familyId, |
| ); |
| if (!stored) { |
| return jsonResp({ error: 'server_error', error_description: 'Token storage failed' }, 500); |
| } |
| return jsonResp({ |
| access_token: accessUuid, |
| token_type: 'Bearer', |
| expires_in: TOKEN_TTL_SECONDS, |
| refresh_token: refreshUuid, |
| scope, |
| }); |
| } |
|
|
| |
| const scope = codeData.scope ?? 'mcp'; |
| const stored = await storeNewTokens( |
| deps.redisPipeline, |
| accessUuid, |
| refreshUuid, |
| codeData.api_key_hash, |
| clientId, |
| scope, |
| familyId, |
| ); |
| if (!stored) { |
| return jsonResp({ error: 'server_error', error_description: 'Token storage failed' }, 500); |
| } |
| return jsonResp({ |
| access_token: accessUuid, |
| token_type: 'Bearer', |
| expires_in: TOKEN_TTL_SECONDS, |
| refresh_token: refreshUuid, |
| scope, |
| }); |
| } |
|
|
| async function handleRefreshToken( |
| params: URLSearchParams, |
| clientId: string | null, |
| deps: TokenHandlerDeps, |
| ): Promise<Response> { |
| const refreshToken = params.get('refresh_token'); |
|
|
| if (!refreshToken || !clientId) { |
| return jsonResp( |
| { |
| error: 'invalid_request', |
| error_description: 'Missing required parameters: refresh_token, client_id', |
| }, |
| 400, |
| ); |
| } |
|
|
| |
| let refreshData: RefreshDataPro | RefreshDataLegacy | null; |
| try { |
| refreshData = (await deps.redisGetDel(`oauth:refresh:${refreshToken}`)) as |
| | RefreshDataPro |
| | RefreshDataLegacy |
| | null; |
| } catch { |
| return jsonResp( |
| { error: 'server_error', error_description: 'Auth service temporarily unavailable. Please retry.' }, |
| 503, |
| ); |
| } |
| if (!refreshData) { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| try { |
| const familyId = await deps.redisGet(refreshFamilyPointerKey(refreshToken)); |
| if (typeof familyId === 'string' && familyId) { |
| const revoked = await markRefreshFamilyRevoked(deps, familyId); |
| if (!revoked) { |
| return jsonResp( |
| { error: 'server_error', error_description: 'Auth service temporarily unavailable. Please retry.' }, |
| 503, |
| ); |
| } |
| } |
| } catch { |
| return jsonResp( |
| { error: 'server_error', error_description: 'Auth service temporarily unavailable. Please retry.' }, |
| 503, |
| ); |
| } |
| return jsonResp( |
| { error: 'invalid_grant', error_description: 'Refresh token is invalid, expired, or already used' }, |
| 400, |
| ); |
| } |
| if (refreshData.client_id !== clientId) { |
| return jsonResp({ error: 'invalid_grant', error_description: 'client_id mismatch' }, 400); |
| } |
|
|
| |
| |
| |
| if (refreshData.family_id) { |
| const pointerStored = await persistRefreshFamilyPointer(deps, refreshToken, refreshData.family_id); |
| if (!pointerStored) { |
| await restoreConsumedRefreshToken(deps, refreshToken, refreshData).catch(() => false); |
| return jsonResp( |
| { error: 'server_error', error_description: 'Auth service temporarily unavailable. Please retry.' }, |
| 503, |
| ); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| if (refreshData.family_id) { |
| let familyRevoked = false; |
| try { |
| familyRevoked = (await deps.redisGet(refreshFamilyRevocationKey(refreshData.family_id))) != null; |
| } catch { |
| await restoreConsumedRefreshToken(deps, refreshToken, refreshData).catch(() => false); |
| return jsonResp( |
| { error: 'server_error', error_description: 'Auth service temporarily unavailable. Please retry.' }, |
| 503, |
| ); |
| } |
| if (familyRevoked) { |
| return jsonResp( |
| { error: 'invalid_grant', error_description: 'Refresh token is invalid, expired, or already used' }, |
| 400, |
| ); |
| } |
| } |
|
|
| const clientCheck = await checkClientExists(deps, clientId); |
| if (clientCheck) return clientCheck; |
|
|
| const accessUuid = deps.randomUuid(); |
| const newRefreshUuid = deps.randomUuid(); |
|
|
| if (refreshData.kind === 'pro') { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const validation: ProMcpValidateUnion = await deps.validateProMcpToken(refreshData.mcpTokenId); |
|
|
| if (validation.ok === 'transient') { |
| |
| |
| |
| |
| |
| try { |
| await restoreConsumedRefreshToken(deps, refreshToken, refreshData); |
| } catch { |
| |
| |
| } |
| return jsonResp( |
| { error: 'server_error', error_description: 'Auth service temporarily unavailable. Please retry.' }, |
| 503, |
| ); |
| } |
|
|
| if (validation.ok === 'revoked' || validation.userId !== refreshData.userId) { |
| |
| |
| |
| |
| return jsonResp( |
| { error: 'invalid_grant', error_description: 'Refresh token is invalid, expired, or already used' }, |
| 400, |
| ); |
| } |
|
|
| const scope = refreshData.scope ?? 'mcp_pro'; |
| const stored = await storeProTokens( |
| deps.redisPipeline, |
| accessUuid, |
| newRefreshUuid, |
| refreshData.userId, |
| refreshData.mcpTokenId, |
| clientId, |
| scope, |
| refreshData.family_id, |
| ); |
| if (!stored) { |
| return jsonResp({ error: 'server_error', error_description: 'Token storage failed' }, 500); |
| } |
| return jsonResp({ |
| access_token: accessUuid, |
| token_type: 'Bearer', |
| expires_in: TOKEN_TTL_SECONDS, |
| refresh_token: newRefreshUuid, |
| scope, |
| }); |
| } |
|
|
| |
| const scope = refreshData.scope ?? 'mcp'; |
| const stored = await storeNewTokens( |
| deps.redisPipeline, |
| accessUuid, |
| newRefreshUuid, |
| refreshData.api_key_hash, |
| clientId, |
| scope, |
| refreshData.family_id, |
| ); |
| if (!stored) { |
| return jsonResp({ error: 'server_error', error_description: 'Token storage failed' }, 500); |
| } |
| return jsonResp({ |
| access_token: accessUuid, |
| token_type: 'Bearer', |
| expires_in: TOKEN_TTL_SECONDS, |
| refresh_token: newRefreshUuid, |
| scope, |
| }); |
| } |
|
|
| async function handleClientCredentials( |
| clientSecret: string | null, |
| deps: TokenHandlerDeps, |
| ): Promise<Response> { |
| if (!(await validateSecret(clientSecret))) { |
| return jsonResp({ error: 'invalid_client', error_description: 'Invalid client credentials' }, 401); |
| } |
| const uuid = deps.randomUuid(); |
| const stored = await storeLegacyToken(deps.redisPipeline, uuid, clientSecret as string); |
| if (!stored) { |
| return jsonResp({ error: 'server_error', error_description: 'Token storage failed' }, 500); |
| } |
| return jsonResp({ |
| access_token: uuid, |
| token_type: 'Bearer', |
| expires_in: TOKEN_TTL_SECONDS, |
| scope: 'mcp', |
| }); |
| } |
|
|
| |
| |
| |
| |
| async function checkClientExists(deps: TokenHandlerDeps, clientId: string): Promise<Response | null> { |
| let client: unknown; |
| try { |
| client = await deps.redisGet(`oauth:client:${clientId}`); |
| } catch { |
| return jsonResp( |
| { error: 'server_error', error_description: 'Auth service temporarily unavailable. Please retry.' }, |
| 503, |
| ); |
| } |
| if (!client) { |
| return jsonResp( |
| { |
| error: 'invalid_client', |
| error_description: 'Client registration not found or expired. Please re-register.', |
| }, |
| 401, |
| ); |
| } |
| |
| deps.redisPipeline([['EXPIRE', `oauth:client:${clientId}`, CLIENT_TTL_SECONDS]]).catch(() => {}); |
| return null; |
| } |
|
|
| async function applyRateLimit( |
| req: Request, |
| grantType: string | null, |
| clientSecret: string | null, |
| clientId: string | null, |
| ): Promise<Response | null> { |
| const rl = getRatelimit(); |
| if (!rl) return null; |
| try { |
| let rlKey: string; |
| if (grantType === 'client_credentials' && clientSecret) { |
| rlKey = `cred:${(await sha256Hex(clientSecret)).slice(0, 8)}`; |
| } else if (clientId) { |
| rlKey = `cid:${clientId}`; |
| } else { |
| rlKey = `ip:${getClientIp(req)}`; |
| } |
| const { success } = await rl.limit(rlKey); |
| if (!success) { |
| return jsonResp( |
| { error: 'rate_limit_exceeded', error_description: 'Too many token requests. Try again later.' }, |
| 429, |
| ); |
| } |
| return null; |
| } catch { |
| return null; |
| } |
| } |
|
|
| export async function tokenHandler(req: Request, deps: TokenHandlerDeps): Promise<Response> { |
| const corsHeaders = getPublicCorsHeaders('POST, OPTIONS'); |
|
|
| if (req.method === 'OPTIONS') { |
| return new Response(null, { status: 204, headers: corsHeaders }); |
| } |
| if (req.method !== 'POST') { |
| return jsonResp({ error: 'method_not_allowed' }, 405); |
| } |
|
|
| const params = new URLSearchParams(await req.text().catch(() => '')); |
| const grantType = params.get('grant_type'); |
| const clientSecret = params.get('client_secret'); |
| const clientId = params.get('client_id'); |
|
|
| const rateLimited = await applyRateLimit(req, grantType, clientSecret, clientId); |
| if (rateLimited) return rateLimited; |
|
|
| if (grantType === 'authorization_code') { |
| return handleAuthorizationCode(params, clientId, deps); |
| } |
| if (grantType === 'refresh_token') { |
| return handleRefreshToken(params, clientId, deps); |
| } |
| if (grantType === 'client_credentials') { |
| return handleClientCredentials(clientSecret, deps); |
| } |
| return jsonResp({ error: 'unsupported_grant_type' }, 400); |
| } |
|
|
| |
| |
| |
|
|
| export default async function handler(req: Request): Promise<Response> { |
| return tokenHandler(req, { |
| redisGetDel: rawRedisGetDel, |
| redisGet: rawRedisGet, |
| redisPipeline: rawRedisPipeline, |
| validateProMcpToken, |
| randomUuid: () => crypto.randomUUID(), |
| }); |
| } |
|
|