"use server"; import { mcpClientsManager } from "lib/ai/mcp/mcp-manager"; import { z } from "zod"; import { McpServerTable } from "lib/db/pg/schema.pg"; import { mcpOAuthRepository, mcpRepository } from "lib/db/repository"; import { canCreateMCP, canManageMCPServer, canShareMCPServer, getCurrentUser, } from "lib/auth/permissions"; export async function selectMcpClientsAction() { // Get current user to filter MCP servers const currentUser = await getCurrentUser(); if (!currentUser) { return []; } // Get all MCP servers the user can access (their own + shared) const accessibleServers = await mcpRepository.selectAllForUser( currentUser.id, ); const accessibleIds = new Set(accessibleServers.map((s) => s.id)); // Get all active clients and filter to only accessible ones const list = await mcpClientsManager.getClients(); return list .filter(({ id }) => accessibleIds.has(id)) .map(({ client, id }) => { const server = accessibleServers.find((s) => s.id === id); return { ...client.getInfo(), id, userId: server?.userId, visibility: server?.visibility, isOwner: server?.userId === currentUser.id, canManage: server ? server.userId === currentUser.id || currentUser.role === "admin" : false, }; }); } export async function selectMcpClientAction(id: string) { const client = await mcpClientsManager.getClient(id); if (!client) { throw new Error("Client not found"); } return { ...client.client.getInfo(), id, }; } export async function saveMcpClientAction( server: typeof McpServerTable.$inferInsert, ) { if (process.env.NOT_ALLOW_ADD_MCP_SERVERS) { throw new Error("Not allowed to add MCP servers"); } // Get current user const currentUser = await getCurrentUser(); if (!currentUser) { throw new Error("You must be logged in to create MCP connections"); } // Check if user has permission to create/edit MCP connections const hasPermission = await canCreateMCP(); if (!hasPermission) { throw new Error("You don't have permission to create MCP connections"); } // Validate name to ensure it only contains alphanumeric characters and hyphens const nameSchema = z.string().regex(/^[a-zA-Z0-9\-]+$/, { message: "Name must contain only alphanumeric characters (A-Z, a-z, 0-9) and hyphens (-)", }); const result = nameSchema.safeParse(server.name); if (!result.success) { throw new Error( "Name must contain only alphanumeric characters (A-Z, a-z, 0-9) and hyphens (-)", ); } // Check for duplicate names if creating a featured server if (server.visibility === "public") { // Only admins can create featured MCP servers const canShare = await canShareMCPServer(); if (!canShare) { throw new Error("Only administrators can feature MCP servers"); } // Check if a featured server with this name already exists const existing = await mcpRepository.existsByServerName(server.name); if (existing && !server.id) { throw new Error("A featured MCP server with this name already exists"); } } // Add userId to the server object const serverWithUser = { ...server, userId: currentUser.id, visibility: server.visibility || "private", }; return mcpClientsManager.persistClient(serverWithUser); } export async function existMcpClientByServerNameAction(serverName: string) { return await mcpRepository.existsByServerName(serverName); } export async function removeMcpClientAction(id: string) { // Get the MCP server to check ownership const mcpServer = await mcpRepository.selectById(id); if (!mcpServer) { throw new Error("MCP server not found"); } // Check if user has permission to delete this specific MCP server const canManage = await canManageMCPServer( mcpServer.userId, mcpServer.visibility, ); if (!canManage) { throw new Error("You don't have permission to delete this MCP connection"); } await mcpClientsManager.removeClient(id); } export async function refreshMcpClientAction(id: string) { await mcpClientsManager.refreshClient(id); } export async function authorizeMcpClientAction(id: string) { await refreshMcpClientAction(id); const client = await mcpClientsManager.getClient(id); if (client?.client.status != "authorizing") { throw new Error("Not Authorizing"); } return client.client.getAuthorizationUrl()?.toString(); } export async function checkTokenMcpClientAction(id: string) { const session = await mcpOAuthRepository.getAuthenticatedSession(id); // for wait connect to mcp server await mcpClientsManager.getClient(id).catch(() => null); return !!session?.tokens; } export async function callMcpToolAction( id: string, toolName: string, input: unknown, ) { return mcpClientsManager.toolCall(id, toolName, input); } export async function callMcpToolByServerNameAction( serverName: string, toolName: string, input: unknown, ) { return mcpClientsManager.toolCallByServerName(serverName, toolName, input); } export async function shareMcpServerAction( id: string, visibility: "public" | "private", ) { // Only admins can feature MCP servers const canShare = await canShareMCPServer(); if (!canShare) { throw new Error("Only administrators can feature MCP servers"); } // Update the visibility of the MCP server await mcpRepository.updateVisibility(id, visibility); return { success: true }; }