Supabase Memory MCP Server

import { loadPyodide } from "pyodide"; import * as path from "path"; import * as fs from "fs"; import { withOutputCapture } from "../../utils/output-capture.js"; import { formatCallToolError, formatCallToolSuccess, contentFormatters, } from "../../formatters/index.js"; import { MIME_TYPES } from "../../lib/mime-types/index.js"; import type { PyodideInterface } from "pyodide"; // Basic mount point configuration interface MountConfig { hostPath: string; mountPoint: string; } interface ResourceInfo { name: string; // File name uri: string; // Full URI (file://....) mimeType: string; // MIME type } class PyodideManager { private static instance: PyodideManager | null = null; private pyodide: PyodideInterface | null = null; private mountPoints: Map<string, MountConfig> = new Map(); private constructor() {} static getInstance(): PyodideManager { if (!PyodideManager.instance) { PyodideManager.instance = new PyodideManager(); } return PyodideManager.instance; } async initialize(packageCacheDir: string): Promise<boolean> { try { console.error("Initializing Pyodide..."); this.pyodide = await loadPyodide({ packageCacheDir, stdout: (text: string) => { console.log("[Python stdout]:", text); }, stderr: (text: string) => { console.error("[Python stderr]:", text); }, jsglobals: { clearInterval, clearTimeout, setInterval, setTimeout, ImageData: {}, document: { getElementById: (id: any) => { if (id.includes("canvas")) return null; else return { addEventListener: () => {}, style: {}, classList: { add: () => {}, remove: () => {} }, setAttribute: () => {}, appendChild: () => {}, remove: () => {}, }; }, createElement: () => ({ addEventListener: () => {}, style: {}, classList: { add: () => {}, remove: () => {} }, setAttribute: () => {}, appendChild: () => {}, remove: () => {}, }), createTextNode: () => ({ addEventListener: () => {}, style: {}, classList: { add: () => {}, remove: () => {} }, setAttribute: () => {}, appendChild: () => {}, remove: () => {}, }), body: { appendChild: () => {}, }, }, }, }); console.error("Pyodide initialized successfully"); return true; } catch (error) { console.error("Failed to initialize Pyodide:", error); return false; } } getPyodide(): PyodideInterface | null { return this.pyodide; } // Mount filesystem directories async mountDirectory(name: string, hostPath: string): Promise<boolean> { if (!this.pyodide) return false; try { const absolutePath = path.resolve(hostPath); // Create directory if it doesn't exist if (!fs.existsSync(absolutePath)) { fs.mkdirSync(absolutePath, { recursive: true }); console.error(`Created directory: ${absolutePath}`); } const mountPoint = `/mnt/${name}`; this.pyodide.FS.mkdirTree(mountPoint); this.pyodide.FS.mount( this.pyodide.FS.filesystems.NODEFS, { root: absolutePath, }, mountPoint ); this.mountPoints.set(name, { hostPath: absolutePath, mountPoint, }); return true; } catch (error) { console.error(`Failed to mount directory ${hostPath}:`, error); return false; } } // Get information about all mount points async getMountPoints() { if (!this.pyodide) { return formatCallToolError("Pyodide not initialized"); } try { const mountPoints = Array.from(this.mountPoints.entries()).map( ([name, config]) => ({ name, hostPath: config.hostPath, mountPoint: config.mountPoint, }) ); return formatCallToolSuccess(JSON.stringify(mountPoints, null, 2)); } catch (error) { return formatCallToolError(error); } } // List contents of a mounted directory async listMountedDirectory(mountName: string) { if (!this.pyodide) { return formatCallToolError("Pyodide not initialized"); } const mountConfig = this.mountPoints.get(mountName); if (!mountConfig) { return formatCallToolError(`Mount point not found: ${mountName}`); } try { // Use Python code to get directory contents const pythonCode = ` import os def list_directory(path): contents = [] try: for item in os.listdir(path): full_path = os.path.join(path, item) if os.path.isfile(full_path): contents.append(f"FILE: {item}") elif os.path.isdir(full_path): contents.append(f"DIR: {item}") except Exception as e: print(f"Error listing directory: {e}") return [] return contents list_directory("${mountConfig.mountPoint}") `; return await this.executePython(pythonCode, 5000); } catch (error) { return formatCallToolError(error); } } /** * Get mount name from file path * @param filePath Full path to check * @returns Mount name if found, null if not matched */ getMountNameFromPath(filePath: string): string | null { if (!filePath) return null; // Normalize path separators const normalizedPath = filePath.replace(/\\/g, "/"); let longestMatch = ""; let matchedMountName: string | null = null; // Check each mount point for (const [mountName, config] of this.mountPoints.entries()) { const normalizedHostPath = config.hostPath.replace(/\\/g, "/"); // Check if path starts with this mount point if (normalizedPath.startsWith(normalizedHostPath)) { // Keep track of the longest matching path if (normalizedHostPath.length > longestMatch.length) { longestMatch = normalizedHostPath; matchedMountName = mountName; } } } return matchedMountName; } /** * Get mount point information from a file URI * @param uri File URI (e.g., "file:///mnt/data/file.txt") * @returns MountPathInfo object or null if not found */ getMountPointInfo(uri: string) { // Remove "file://" prefix let filePath = uri.replace("file://", ""); // Find matching mount point for (const [mountName, config] of this.mountPoints.entries()) { const mountPoint = config.mountPoint; // Check if path starts with this mount point if (filePath.startsWith(mountPoint)) { // Get relative path by removing mount point prefix const relativePath = filePath .slice(mountPoint.length) .replace(/^[/\\]+/, ""); // Remove leading slashes return { mountName, mountPoint, relativePath, }; } } return null; } async executePython(code: string, timeout: number) { if (!this.pyodide) { return formatCallToolError("Pyodide not initialized"); } try { const { result, output } = await withOutputCapture( this.pyodide, async () => { const executionResult = await Promise.race([ this.pyodide!.runPythonAsync(code), new Promise((_, reject) => setTimeout(() => reject(new Error("Execution timeout")), timeout) ), ]); // Memory cleanup this.pyodide!.globals.clear(); await this.pyodide!.runPythonAsync("import gc; gc.collect()"); return executionResult; }, { suppressConsole: true } ); return formatCallToolSuccess( output ? `Output:\n${output}\nResult:\n${String(result)}` : String(result) ); } catch (error) { return formatCallToolError(error); } } async installPackage(packageName: string) { if (!this.pyodide) { return formatCallToolError("Pyodide not initialized"); } try { const { output } = await withOutputCapture( this.pyodide, async () => { await this.pyodide!.loadPackage(packageName, { messageCallback: (msg: string) => console.log(msg), errorCallback: (err: string) => console.error(err), }); }, { suppressConsole: true } ); return formatCallToolSuccess(output); } catch (error) { return formatCallToolError(error); } } async readResource( mountName: string, resourcePath: string ): Promise< | { blob: string; mimeType: string; } | { error: string } > { if (!this.pyodide) { return { error: "Pyodide not initialized" }; } const mountConfig = this.mountPoints.get(mountName); if (!mountConfig) { return { error: `Mount point not found: ${mountName}` }; } try { // Get full path to the image const fullPath = path.join(mountConfig.hostPath, resourcePath); if (!fs.existsSync(fullPath)) { return { error: `Image file not found: ${fullPath}` }; } // Get MIME type from file extension const ext = path.extname(fullPath).toLowerCase(); const mimeType = MIME_TYPES[ext]; if (!mimeType) { return { error: `Unsupported image format: ${ext}` }; } // Read and encode image const imageBuffer = await fs.promises.readFile(fullPath); const base64Data = imageBuffer.toString("base64"); return { blob: base64Data, mimeType }; } catch (error) { return { error: String(error) }; } } /** * List all files matching the given MIME types across all mount points * @param mimeTypes Array of MIME types to match (e.g., ['image/jpeg', 'image/png']) * @returns Array of ResourceInfo objects */ async listResources(): Promise<ResourceInfo[]> { const resources: ResourceInfo[] = []; const validMimeTypes = new Set(Object.values(MIME_TYPES)); const isMatchingMimeType = (filePath: string): string | null => { const ext = path.extname(filePath).toLowerCase(); const mimeType = MIME_TYPES[ext]; return mimeType && validMimeTypes.has(mimeType) ? mimeType : null; }; const scanDirectory = (dirPath: string): void => { try { const items = fs.readdirSync(dirPath); const mountName = this.getMountNameFromPath(dirPath); if (!mountName) return; const config = this.mountPoints.get(mountName); if (!config) return; const { hostPath, mountPoint } = config; for (const item of items) { const fullPath = path.join(dirPath, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { // Recursively scan subdirectories scanDirectory(fullPath); } else if (stat.isFile()) { // Check if file matches MIME type const mimeType = isMatchingMimeType(item); if (mimeType) { // Calculate relative path from hostPath const relativePath = path.relative(hostPath, fullPath); // Construct URI with full path const uri = `file://${path.join(mountPoint, relativePath)}`; resources.push({ name: item, uri, mimeType, }); } } } } catch (error) { console.error(`Error scanning directory ${dirPath}:`, error); } }; // Scan all mount points for (const [_, config] of this.mountPoints.entries()) { scanDirectory(config.hostPath); } return resources; } async readImage(mountName: string, imagePath: string) { if (!this.pyodide) { return formatCallToolError("Pyodide not initialized"); } try { const resource = await this.readResource(mountName, imagePath); if ("error" in resource) { return formatCallToolError(resource.error); } const content = contentFormatters.formatImage( resource.blob, resource.mimeType ); return formatCallToolSuccess(content); } catch (error) { return formatCallToolError(error); } } } export { PyodideManager };