'use server' import { auth } from '@/auth' import { prisma } from '@/lib/prisma' import { revalidatePath } from 'next/cache' import { createHash, randomBytes } from 'crypto' const KEY_PREFIX = 'mcp_key_' function hashKey(rawKey: string): string { return createHash('sha256').update(rawKey).digest('hex') } export type McpKeyInfo = { shortId: string name: string userId: string userName: string active: boolean createdAt: string lastUsedAt: string | null } /** * List all MCP API keys for the current user. */ export async function listMcpKeys(): Promise { const session = await auth() if (!session?.user?.id) throw new Error('Unauthorized') const allKeys = await prisma.systemConfig.findMany({ where: { key: { startsWith: KEY_PREFIX } }, }) const keys: McpKeyInfo[] = [] for (const entry of allKeys) { try { const info = JSON.parse(entry.value) if (info.userId !== session.user.id) continue keys.push({ shortId: info.shortId, name: info.name, userId: info.userId, userName: info.userName, active: info.active, createdAt: info.createdAt, lastUsedAt: info.lastUsedAt, }) } catch { // skip invalid JSON } } return keys } /** * Generate a new MCP API key for the current user. * Returns the raw key (shown only once) and key info. */ export async function generateMcpKey(name: string): Promise<{ rawKey: string; info: { shortId: string; name: string } }> { const session = await auth() if (!session?.user?.id) throw new Error('Unauthorized') const user = await prisma.user.findUnique({ where: { id: session.user.id }, select: { id: true, name: true, email: true }, }) if (!user) throw new Error('User not found') const rawBytes = randomBytes(24) const shortId = rawBytes.toString('hex').substring(0, 8) const rawKey = `mcp_sk_${rawBytes.toString('hex')}` const keyHash = hashKey(rawKey) const keyInfo = { shortId, name: name || `Key for ${user.name}`, userId: user.id, userName: user.name, userEmail: user.email, keyHash, createdAt: new Date().toISOString(), lastUsedAt: null, active: true, } await prisma.systemConfig.create({ data: { key: `${KEY_PREFIX}${shortId}`, value: JSON.stringify(keyInfo), }, }) revalidatePath('/settings/mcp') return { rawKey, info: { shortId, name: keyInfo.name }, } } /** * Revoke (deactivate) an MCP API key. Only the owner can revoke. */ export async function revokeMcpKey(shortId: string): Promise { const session = await auth() if (!session?.user?.id) throw new Error('Unauthorized') const configKey = `${KEY_PREFIX}${shortId}` const entry = await prisma.systemConfig.findUnique({ where: { key: configKey } }) if (!entry) throw new Error('Key not found') const info = JSON.parse(entry.value) if (info.userId !== session.user.id) throw new Error('Forbidden') if (!info.active) return false info.active = false info.revokedAt = new Date().toISOString() await prisma.systemConfig.update({ where: { key: configKey }, data: { value: JSON.stringify(info) }, }) revalidatePath('/settings/mcp') return true } /** * Permanently delete an MCP API key. Only the owner can delete. */ export async function deleteMcpKey(shortId: string): Promise { const session = await auth() if (!session?.user?.id) throw new Error('Unauthorized') const configKey = `${KEY_PREFIX}${shortId}` const entry = await prisma.systemConfig.findUnique({ where: { key: configKey } }) if (!entry) throw new Error('Key not found') const info = JSON.parse(entry.value) if (info.userId !== session.user.id) throw new Error('Forbidden') try { await prisma.systemConfig.delete({ where: { key: configKey } }) revalidatePath('/settings/mcp') return true } catch { return false } } export type McpServerStatus = { mode: 'stdio' | 'sse' | 'unknown' url: string | null } /** * Get MCP server status — mode and URL. */ export async function getMcpServerStatus(): Promise { // Check if SSE mode is configured via env const mode = process.env.MCP_SERVER_MODE === 'sse' ? 'sse' : 'stdio' const url = process.env.MCP_SERVER_URL || null return { mode, url } }