diff --git a/apps/dbagent/src/app/(main)/projects/[project]/mcp/[server]/page.tsx b/apps/dbagent/src/app/(main)/projects/[project]/mcp/[server]/page.tsx index cf726f58..a51b5d57 100644 --- a/apps/dbagent/src/app/(main)/projects/[project]/mcp/[server]/page.tsx +++ b/apps/dbagent/src/app/(main)/projects/[project]/mcp/[server]/page.tsx @@ -1,10 +1,6 @@ -import { promises as fs } from 'fs'; import { notFound } from 'next/navigation'; -import path from 'path'; -import { actionGetUserMcpServer } from '~/components/mcp/action'; import { McpView } from '~/components/mcp/mcp-view'; -import { getMCPSourceDir } from '~/lib/ai/tools/user-mcp'; -import { MCPServerInsert } from '~/lib/db/schema'; +import { findServerOnDisk } from '~/lib/db/mcp-servers'; type PageParams = { project: string; @@ -13,57 +9,8 @@ type PageParams = { export default async function McpServerPage({ params }: { params: Promise }) { const { server: serverId } = await params; - const mcpSourceDir = getMCPSourceDir(); - - // Check if server file exists locally - const filePath = path.join(mcpSourceDir, `${serverId}.ts`); - let server: MCPServerInsert | null = null; - - try { - // Try to read the local file first - await fs.access(filePath); - const content = await fs.readFile(filePath, 'utf-8'); - - // Extract metadata from file content - const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/); - const versionMatch = content.match(/version:\s*['"]([^'"]+)['"]/); - - server = { - name: serverId, - serverName: nameMatch?.[1] ?? serverId, - version: versionMatch?.[1] ?? '1.0.0', - filePath: `${serverId}.ts`, - enabled: false - }; - - // Try to get additional data from database if it exists - try { - const dbServer = await actionGetUserMcpServer(serverId); - if (dbServer && server) { - server.enabled = dbServer.enabled; - } - } catch (error) { - // Ignore database errors, we'll use the local file data - console.error('Error fetching server from database:', error); - } - } catch (error) { - // If local file doesn't exist, try database - try { - const dbServer = await actionGetUserMcpServer(serverId); - if (dbServer) { - server = { - name: dbServer.name, - serverName: dbServer.serverName, - version: dbServer.version, - filePath: dbServer.filePath, - enabled: dbServer.enabled - }; - } - } catch (error) { - console.error('Error fetching server from database:', error); - } - } + const server = await findServerOnDisk(serverId); if (!server) { notFound(); } diff --git a/apps/dbagent/src/app/(main)/projects/[project]/mcp/create/page.tsx b/apps/dbagent/src/app/(main)/projects/[project]/mcp/create/page.tsx deleted file mode 100644 index 1fd33557..00000000 --- a/apps/dbagent/src/app/(main)/projects/[project]/mcp/create/page.tsx +++ /dev/null @@ -1,125 +0,0 @@ -'use client'; - -import { Button } from '@xata.io/components'; -import { UploadIcon } from 'lucide-react'; -import { useRouter } from 'next/navigation'; -import { useCallback, useState } from 'react'; - -export default function CreateMcpPage() { - const router = useRouter(); - const [isSubmitting, setIsSubmitting] = useState(false); - const [isDragging, setIsDragging] = useState(false); - const [file, setFile] = useState(null); - const [error, setError] = useState(null); - - const validateFile = (file: File): boolean => { - if (!file.name.endsWith('.ts')) { - setError('Only TypeScript (.ts) files are allowed'); - return false; - } - setError(null); - return true; - }; - - const handleDragOver = useCallback((e: React.DragEvent) => { - e.preventDefault(); - setIsDragging(true); - }, []); - - const handleDragLeave = useCallback((e: React.DragEvent) => { - e.preventDefault(); - setIsDragging(false); - }, []); - - const handleDrop = useCallback((e: React.DragEvent) => { - e.preventDefault(); - setIsDragging(false); - - const droppedFile = e.dataTransfer.files[0]; - if (droppedFile && validateFile(droppedFile)) { - setFile(droppedFile); - } - }, []); - - const handleFileSelect = useCallback((e: React.ChangeEvent) => { - const selectedFile = e.target.files?.[0]; - if (selectedFile && validateFile(selectedFile)) { - setFile(selectedFile); - } - }, []); - - const handleSubmit = async () => { - if (!file) return; - - setIsSubmitting(true); - setError(null); - - try { - const formData = new FormData(); - formData.append('file', file); - - const response = await fetch('/api/mcp/servers/upload', { - method: 'POST', - body: formData - }); - - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.message || 'Failed to upload file'); - } - - router.back(); - } catch (error) { - console.error('Error uploading file:', error); - setError(error instanceof Error ? error.message : 'Failed to upload file'); - } finally { - setIsSubmitting(false); - } - }; - - return ( -
-
-

Create MCP Server

-

Upload a TypeScript file to create a new MCP server.

-
- -
-
-
- -
- {file ? ( -

Selected file: {file.name}

- ) : ( -

Drag and drop a TypeScript file here, or click to select

- )} -
- - -
-
- - {error &&
{error}
} - -
- - -
-
-
- ); -} diff --git a/apps/dbagent/src/app/api/mcp/servers/[server]/route.ts b/apps/dbagent/src/app/api/mcp/servers/[server]/route.ts index f753b798..309e14fb 100644 --- a/apps/dbagent/src/app/api/mcp/servers/[server]/route.ts +++ b/apps/dbagent/src/app/api/mcp/servers/[server]/route.ts @@ -1,39 +1,13 @@ -import { promises as fs } from 'fs'; import { NextResponse } from 'next/server'; -import path from 'path'; -import { getMCPSourceDir } from '~/lib/ai/tools/user-mcp'; - -const mcpSourceDir = getMCPSourceDir(); +import { findServerOnDisk } from '~/lib/db/mcp-servers'; export async function GET(_: Request, { params }: { params: Promise<{ server: string }> }) { try { const { server } = await params; - const filePath = path.join(mcpSourceDir, `${server}.ts`); - - // Check if file exists - try { - await fs.access(filePath); - } catch (error) { + const metadata = await findServerOnDisk(server); + if (!metadata) { return NextResponse.json({ error: 'Server file not found' }, { status: 404 }); } - - // Read file content - const content = await fs.readFile(filePath, 'utf-8'); - - // Extract metadata from file content - const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/); - const versionMatch = content.match(/version:\s*['"]([^'"]+)['"]/); - const descriptionMatch = content.match(/description:\s*['"]([^'"]+)['"]/); - - const metadata = { - name: server, - serverName: nameMatch ? nameMatch[1] : server, - version: versionMatch ? versionMatch[1] : '1.0.0', - description: descriptionMatch ? descriptionMatch[1] : '', - filePath: `${server}.ts`, - enabled: false - }; - return NextResponse.json(metadata); } catch (error) { console.error('Error reading server file:', error); diff --git a/apps/dbagent/src/app/api/mcp/servers/route.ts b/apps/dbagent/src/app/api/mcp/servers/route.ts index 10f50d24..e98c9988 100644 --- a/apps/dbagent/src/app/api/mcp/servers/route.ts +++ b/apps/dbagent/src/app/api/mcp/servers/route.ts @@ -1,29 +1,19 @@ import { promises as fs } from 'fs'; import { NextResponse } from 'next/server'; -import path from 'path'; -import { getMCPSourceDir } from '~/lib/ai/tools/user-mcp'; +import { getMCPServersDir } from '~/lib/ai/tools/user-mcp'; -const mcpSourceDir = getMCPSourceDir(); +const mcpServersDir = getMCPServersDir(); export async function GET() { try { - const files = await fs.readdir(mcpSourceDir); - const serverFiles = files.filter((file) => file.endsWith('.ts') && !file.endsWith('.d.ts')); + const files = await fs.readdir(mcpServersDir); + const serverFiles = files.filter((file) => file.endsWith('.js')); const servers = await Promise.all( serverFiles.map(async (file) => { - const filePath = path.join(mcpSourceDir, file); - const content = await fs.readFile(filePath, 'utf-8'); - - // Extract server name and version from the file content - const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/); - const versionMatch = content.match(/version:\s*['"]([^'"]+)['"]/); - return { - name: path.basename(file, '.ts'), - serverName: nameMatch ? nameMatch[1] : path.basename(file, '.ts'), - version: versionMatch ? versionMatch[1] : '1.0.0', - filePath: file, + name: file.endsWith('.js') ? file.slice(0, -3) : file, + serverName: file, enabled: false }; }) diff --git a/apps/dbagent/src/app/api/mcp/servers/upload/route.ts b/apps/dbagent/src/app/api/mcp/servers/upload/route.ts deleted file mode 100644 index af55236e..00000000 --- a/apps/dbagent/src/app/api/mcp/servers/upload/route.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { exec } from 'child_process'; -import { promises as fs } from 'fs'; -import { NextResponse } from 'next/server'; -import path from 'path'; -import { promisify } from 'util'; - -const execAsync = promisify(exec); - -export async function POST(request: Request) { - try { - const formData = await request.formData(); - const file = formData.get('file') as File; - - if (!file) { - return NextResponse.json({ message: 'No file provided' }, { status: 400 }); - } - - if (!file.name.endsWith('.ts')) { - return NextResponse.json({ message: 'Only TypeScript (.ts) files are allowed' }, { status: 400 }); - } - - // Ensure the mcp-source directory exists - const mcpSourceDir = path.join(process.cwd(), 'mcp-source'); - try { - await fs.access(mcpSourceDir); - } catch { - throw new Error( - 'MCP source directory does not exist. Please ensure the mcp-source directory is present in the project root.' - ); - } - - // Read the file content - const fileContent = await file.text(); - - // Write the file to mcp-source directory - const filePath = path.join(mcpSourceDir, file.name); - await fs.writeFile(filePath, fileContent); - - // Run the build command - try { - await execAsync('pnpm build:mcp-servers'); - } catch (buildError) { - console.error('Error building MCP servers:', buildError); - return NextResponse.json({ message: 'File uploaded but build failed' }, { status: 500 }); - } - - return NextResponse.json({ message: 'File uploaded and built successfully' }, { status: 200 }); - } catch (error) { - console.error('Error uploading file:', error); - return NextResponse.json({ message: 'Failed to upload file' }, { status: 500 }); - } -} diff --git a/apps/dbagent/src/components/aws-integration/aws-integration.tsx b/apps/dbagent/src/components/aws-integration/aws-integration.tsx index 3931e396..39a2bb50 100644 --- a/apps/dbagent/src/components/aws-integration/aws-integration.tsx +++ b/apps/dbagent/src/components/aws-integration/aws-integration.tsx @@ -19,7 +19,7 @@ import { SelectValue, toast } from '@xata.io/components'; -import { AlertCircle, Loader2 } from 'lucide-react'; +import { Loader2 } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useState } from 'react'; import { RDSClusterDetailedInfo, RDSClusterInfo } from '~/lib/aws/rds'; @@ -130,7 +130,6 @@ export function AWSIntegration({ projectId, connections }: { projectId: string;
- Add an IAM policy and user To obtain the Access Key ID and Secret Access Key,{' '} diff --git a/apps/dbagent/src/components/gcp-integration/gcp-integration.tsx b/apps/dbagent/src/components/gcp-integration/gcp-integration.tsx index b60d20b3..4d67a34d 100644 --- a/apps/dbagent/src/components/gcp-integration/gcp-integration.tsx +++ b/apps/dbagent/src/components/gcp-integration/gcp-integration.tsx @@ -22,7 +22,7 @@ import { Textarea, toast } from '@xata.io/components'; -import { AlertCircle, Loader2 } from 'lucide-react'; +import { Loader2 } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useState } from 'react'; import { Connection } from '~/lib/db/schema'; @@ -115,7 +115,6 @@ export function GCPIntegration({ projectId, connections }: { projectId: string;
- Create a service account To create a GCP service account for the Agent, follow this guide:{' '} diff --git a/apps/dbagent/src/components/mcp/action.ts b/apps/dbagent/src/components/mcp/action.ts index dbab1add..6c1ea6e4 100644 --- a/apps/dbagent/src/components/mcp/action.ts +++ b/apps/dbagent/src/components/mcp/action.ts @@ -1,10 +1,7 @@ 'use server'; -import { promises as fs } from 'fs'; -import path from 'path'; -import { getMCPSourceDir, getMCPSourceDistDir } from '~/lib/ai/tools/user-mcp'; import { getUserSessionDBAccess } from '~/lib/db/db'; -import { addUserMcpServerToDB, deleteUserMcpServer, getUserMcpServer, updateUserMcpServer } from '~/lib/db/mcp-servers'; +import { addUserMcpServerToDB, getUserMcpServer, updateUserMcpServer } from '~/lib/db/mcp-servers'; import { MCPServer, MCPServerInsert } from '~/lib/db/schema'; //playbook db insert @@ -24,6 +21,9 @@ export async function actionCheckUserMcpServerExists(serverName: string): Promis } export async function actionUpdateUserMcpServer(input: MCPServerInsert) { + if (!input.filePath) { + input.filePath = `${input.name}.js`; + } const dbAccess = await getUserSessionDBAccess(); return await updateUserMcpServer(dbAccess, input); } @@ -32,37 +32,3 @@ export async function actionGetUserMcpServer(serverName: string) { const dbAccess = await getUserSessionDBAccess(); return await getUserMcpServer(dbAccess, serverName); } - -export async function actionDeleteUserMcpServerFromDBAndFiles(serverName: string): Promise { - const dbAccess = await getUserSessionDBAccess(); - - // Get the server details before deleting from DB - const server = await getUserMcpServer(dbAccess, serverName); - if (server) { - await deleteUserMcpServer(dbAccess, serverName); - } - - // Delete the files - const mcpSourceDir = getMCPSourceDir(); - const mcpSourceDistDir = getMCPSourceDistDir(); - - try { - // Delete .ts file - const tsFilePath = path.join(mcpSourceDir, `${serverName}.ts`); - await fs.unlink(tsFilePath); - - // Delete .js file if it exists - const jsFilePath = path.join(mcpSourceDistDir, `${serverName}.js`); - try { - await fs.unlink(jsFilePath); - } catch (error) { - // Ignore error if .js file doesn't exist - if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - throw error; - } - } - } catch (error) { - console.error('Error deleting server files:', error); - // Don't throw the error since the DB deletion was successful - } -} diff --git a/apps/dbagent/src/components/mcp/mcp-table.tsx b/apps/dbagent/src/components/mcp/mcp-table.tsx index e0dd9d16..45b31d54 100644 --- a/apps/dbagent/src/components/mcp/mcp-table.tsx +++ b/apps/dbagent/src/components/mcp/mcp-table.tsx @@ -1,13 +1,12 @@ 'use client'; import { + Alert, + AlertDescription, + AlertTitle, Badge, Button, Code, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, Switch, Table, TableBody, @@ -19,7 +18,7 @@ import { TooltipContent, TooltipTrigger } from '@xata.io/components'; -import { BookOpenIcon, ChevronLeftIcon, ChevronRightIcon, MoreVerticalIcon } from 'lucide-react'; +import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'; import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -41,8 +40,6 @@ export function McpTable() { const { project } = useParams<{ project: string }>(); const [currentPage, setCurrentPage] = useState(1); - const [isAddButtonDisabled, setIsAddButtonDisabled] = useState(false); - const loadMcpServers = async () => { try { const response = await fetch('/api/mcp/servers'); @@ -99,10 +96,6 @@ export function McpTable() { void loadMcpServers(); }, [project]); - useEffect(() => { - setIsAddButtonDisabled(false); - }, []); - const SkeletonRow = () => ( @@ -124,15 +117,30 @@ export function McpTable() {

MCP Servers

-
+ +
+ + Add custom tools via a new MCP server + + To add custom tools, you can create a new MCP server, which the Agent will run locally. To create a new MCP + server,{' '} + + follow this guide + + . + + +
+ Sever Name - File Enabled Actions @@ -150,7 +158,7 @@ export function McpTable() {
- {server.serverName} + {server.name} {!mcpServerInDb[server.name] && ( @@ -164,26 +172,13 @@ export function McpTable() { )}
- {server.filePath} handleToggleEnabled(server)} /> -
- - - - - - router.push(getMcpServerUrl(server))}> - - View Details - - - -
+
))} diff --git a/apps/dbagent/src/components/mcp/mcp-view.tsx b/apps/dbagent/src/components/mcp/mcp-view.tsx index 4f9c7dc7..a6369441 100644 --- a/apps/dbagent/src/components/mcp/mcp-view.tsx +++ b/apps/dbagent/src/components/mcp/mcp-view.tsx @@ -4,8 +4,6 @@ import { Button, Card, CardContent, - CardDescription, - CardFooter, CardHeader, CardTitle, Input, @@ -14,17 +12,13 @@ import { TooltipContent, TooltipTrigger } from '@xata.io/components'; -import { ArrowLeft, PlayCircle, PlusIcon, Trash2Icon, XIcon } from 'lucide-react'; +import { ArrowLeft, PlayCircle, PlusIcon, XIcon } from 'lucide-react'; import Link from 'next/link'; -import { useParams, useRouter } from 'next/navigation'; +import { useParams } from 'next/navigation'; import { useEffect, useState } from 'react'; import { actionGetConnections, actionGetCustomToolsFromMCPServer } from '~/components/tools/action'; import { Connection, MCPServerInsert } from '~/lib/db/schema'; -import { - actionCheckUserMcpServerExists, - actionDeleteUserMcpServerFromDBAndFiles, - actionUpdateUserMcpServer -} from './action'; +import { actionCheckUserMcpServerExists, actionUpdateUserMcpServer } from './action'; interface Tool { name: string; @@ -34,12 +28,10 @@ interface Tool { export function McpView({ server: initialServer }: { server: MCPServerInsert }) { const { project } = useParams<{ project: string }>(); - const router = useRouter(); const [server, setServer] = useState(initialServer); const [tools, setTools] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [isInDb, setIsInDb] = useState(false); const [isCheckingDb, setIsCheckingDb] = useState(true); const [envVars, setEnvVars] = useState>(initialServer.envVars || {}); @@ -82,16 +74,6 @@ export function McpView({ server: initialServer }: { server: MCPServerInsert }) void loadData(); }, [project, server.name, server.enabled]); - const handleDeleteServer = async () => { - try { - await actionDeleteUserMcpServerFromDBAndFiles(server.name); - router.push(`/projects/${project}/mcp`); - } catch (error) { - console.error('Error deleting server:', error); - setError('Failed to delete server. Please try again later.'); - } - }; - const handleAddEnvVar = () => { setEnvVars({ ...envVars, '': '' }); }; @@ -146,9 +128,6 @@ export function McpView({ server: initialServer }: { server: MCPServerInsert }) MCP Server: {server.serverName} - -

Version: {server.version}

-
@@ -261,23 +240,6 @@ export function McpView({ server: initialServer }: { server: MCPServerInsert }) )}
- - {!showDeleteConfirm ? ( - - ) : ( -
- - -
- )} -
); diff --git a/apps/dbagent/src/components/slack-integration/slack-integration.tsx b/apps/dbagent/src/components/slack-integration/slack-integration.tsx index d826becc..2fccf7d4 100644 --- a/apps/dbagent/src/components/slack-integration/slack-integration.tsx +++ b/apps/dbagent/src/components/slack-integration/slack-integration.tsx @@ -15,7 +15,6 @@ import { toast, useForm } from '@xata.io/components'; -import { AlertCircle } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useState } from 'react'; import { actionGetWebhookUrl, actionSaveWebhookUrl } from './actions'; @@ -73,7 +72,6 @@ export function SlackIntegration({ projectId }: { projectId: string }) {
- Create a Slack Webhook To create an incoming webhook for posting to your Slack workspace,{' '} diff --git a/apps/dbagent/src/components/ui/side-nav.tsx b/apps/dbagent/src/components/ui/side-nav.tsx index 0a867416..456a1f5a 100644 --- a/apps/dbagent/src/components/ui/side-nav.tsx +++ b/apps/dbagent/src/components/ui/side-nav.tsx @@ -36,7 +36,6 @@ import { AlarmClock, CloudIcon, DatabaseIcon, - Drill, HistoryIcon, MessageSquare, MoreVertical, @@ -202,12 +201,6 @@ export function SideNav({ className, project, onboardingComplete }: SideNavProps icon: NotebookPen, className: 'text-sm' }, - { - title: 'Tools', - url: `${basePath}/tools`, - icon: Drill, - className: 'text-sm' - }, { title: 'MCP', url: `${basePath}/mcp`, diff --git a/apps/dbagent/src/lib/ai/tools/user-mcp.ts b/apps/dbagent/src/lib/ai/tools/user-mcp.ts index 5332b6d8..3a29ec16 100644 --- a/apps/dbagent/src/lib/ai/tools/user-mcp.ts +++ b/apps/dbagent/src/lib/ai/tools/user-mcp.ts @@ -5,20 +5,15 @@ import path from 'path'; import { actionGetUserMcpServer } from '~/components/mcp/action'; import { env } from '~/lib/env/server'; -export function getMCPSourceDistDir() { - const baseDir = env.MCP_SOURCE_DIR || 'mcp-source'; - return path.join(process.cwd(), baseDir, 'dist'); -} - -export function getMCPSourceDir() { - const baseDir = env.MCP_SOURCE_DIR || 'mcp-source'; +export function getMCPServersDir() { + const baseDir = env.MCP_SERVERS_DIR || 'mcp-source/dist'; return path.join(process.cwd(), baseDir); } async function listMCPTools(): Promise { //gets all the enabled mcp servers tools by checking the enabled status from the db try { - const mcpSourceDistDir = getMCPSourceDistDir(); + const mcpSourceDistDir = getMCPServersDir(); const files = await fs.readdir(mcpSourceDistDir); const mcpServerFiles = files.filter((file) => file.endsWith('.js')); @@ -48,10 +43,10 @@ async function listMCPTools(): Promise { async function getMCPToolForServer(serverFileName: string): Promise { try { - const mcpSourceDistDir = getMCPSourceDistDir(); - //only gets tools for a certain mcp server if a serverFileName is provided - //used in mcp-view when getting mcp tools for non-enabled servers that are not in the db - //later when in mcp-view the tools are allowed to be ran only if the mcp server is enabled + const mcpSourceDistDir = getMCPServersDir(); + // only gets tools for a certain mcp server if a serverFileName is provided + // used in mcp-view when getting mcp tools for non-enabled servers that are not in the db + // later when in mcp-view the tools are allowed to be ran only if the mcp server is enabled const filePath = path.join(mcpSourceDistDir, `${serverFileName}.js`); return await loadToolsFromFile(filePath); } catch (error) { diff --git a/apps/dbagent/src/lib/db/mcp-servers.ts b/apps/dbagent/src/lib/db/mcp-servers.ts index 5ca61373..a0f7e7ba 100644 --- a/apps/dbagent/src/lib/db/mcp-servers.ts +++ b/apps/dbagent/src/lib/db/mcp-servers.ts @@ -1,8 +1,11 @@ 'use server'; import { eq } from 'drizzle-orm'; +import { promises as fs } from 'fs'; +import path from 'path'; import { DBAccess } from '~/lib/db/db'; import { MCPServer, MCPServerInsert, mcpServers } from '~/lib/db/schema'; +import { getMCPServersDir } from '../ai/tools/user-mcp'; export async function getUserMcpServers(dbAccess: DBAccess) { return await dbAccess.query(async ({ db }) => { @@ -20,7 +23,33 @@ export async function getUserMcpServer(dbAccess: DBAccess, serverName: string) { }); } -//might need to update this for version and filepath aswell +export async function findServerOnDisk(server: string): Promise { + const mcpServersDir = getMCPServersDir(); + + // Ensure server name is safe and only contains alphanumeric characters, dots, and hyphens + const sanitizedServer = server.replace(/[^a-zA-Z0-9.-]/g, ''); + const filePath = path.join(mcpServersDir, `${sanitizedServer}.js`); + if (!filePath.startsWith(mcpServersDir) || filePath.includes('..')) { + return null; + } + + // Check if file exists + try { + await fs.access(filePath); + } catch (error) { + return null; + } + + const metadata = { + name: sanitizedServer, + serverName: sanitizedServer, + filePath: `${sanitizedServer}.js`, + enabled: false, + version: '0.0.0' // not used + }; + return metadata; +} + export async function updateUserMcpServer(dbAccess: DBAccess, input: MCPServerInsert) { return await dbAccess.query(async ({ db }) => { const result = await db @@ -33,7 +62,7 @@ export async function updateUserMcpServer(dbAccess: DBAccess, input: MCPServerIn .returning(); if (result.length === 0) { - throw new Error(`[UPDATE]Server with name "${input.name}" not found`); + throw new Error(`[UPDATE] Server with name "${input.name}" not found`); } return result[0]; @@ -55,8 +84,8 @@ export async function addUserMcpServerToDB(dbAccess: DBAccess, input: MCPServer) .values({ name: input.name, serverName: input.serverName, - version: input.version, - filePath: input.filePath, + version: '0.0.0', // not used + filePath: `${input.name}.js`, enabled: input.enabled }) .returning(); diff --git a/apps/dbagent/src/lib/env/server.ts b/apps/dbagent/src/lib/env/server.ts index 3daf17b7..09096cd1 100644 --- a/apps/dbagent/src/lib/env/server.ts +++ b/apps/dbagent/src/lib/env/server.ts @@ -8,8 +8,7 @@ const schema = z.object({ DATABASE_URL: z.string(), // MCP settings - MCP_SOURCE_DIR: z.string().optional(), - + MCP_SERVERS_DIR: z.string().optional(), // The OpenID client settings AUTH_SECRET: z.string().optional(), AUTH_OPENID_ID: z.string().optional(), diff --git a/docker-compose.yml b/docker-compose.yml index 364983a1..70d50234 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,12 +12,15 @@ services: - db_network xata-agent: - image: xataio/agent:0.2.2 + image: xataio/agent:0.3.1 environment: DATABASE_URL: postgresql://${POSTGRES_USER:-dbagent}:${POSTGRES_PASSWORD:-changeme}@postgres:5432/${POSTGRES_DB:-dbagent} NODE_ENV: production AUTH_TRUST_HOST: ${AUTH_TRUST_HOST:-localhost} + MCP_SERVERS_DIR: mcp-servers env_file: '.env.production' + volumes: + - ./mcp-servers:/app/apps/dbagent/mcp-servers ports: - '8080:8080' depends_on: