Skip to main content
Glama

3D Asset Processing MCP

by GeoLibra
file-handler.ts5.84 kB
import fs from 'fs-extra'; import path from 'path'; import { createHash } from 'crypto'; import { v4 as uuidv4 } from 'uuid'; import { ModelInput } from '../types'; import logger from './logger'; import { GLB_EXT, GLTF_EXT } from './gltf-constants'; export class FileHandler { private tempDir: string; constructor(tempDir = './temp') { this.tempDir = path.resolve(tempDir); this.ensureTempDir(); } private async ensureTempDir(): Promise<void> { try { await fs.ensureDir(this.tempDir); logger.debug(`Temp directory ensured: ${this.tempDir}`); } catch (error) { logger.error('Failed to create temp directory:', error); throw error; } } /** * Process input files, converting them to local file paths */ async processInput(input: ModelInput): Promise<string> { switch (input.type) { case 'file': return this.processFile(input.source); case 'url': return this.downloadFile(input.source); case 'base64': return this.saveBase64(input.source); default: throw new Error(`Unsupported input type: ${input.type}`); } } /** * Process local file */ private async processFile(filePath: string): Promise<string> { const resolvedPath = path.resolve(filePath); if (!await fs.pathExists(resolvedPath)) { throw new Error(`File not found: ${filePath}`); } const stats = await fs.stat(resolvedPath); if (!stats.isFile()) { throw new Error(`Path is not a file: ${filePath}`); } logger.debug(`Processing local file: ${resolvedPath}`); return resolvedPath; } /** * Download remote file */ private async downloadFile(url: string): Promise<string> { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const buffer = await response.arrayBuffer(); const fileName = this.getFileNameFromUrl(url) || `download_${uuidv4()}`; const filePath = path.join(this.tempDir, fileName); await fs.writeFile(filePath, Buffer.from(buffer)); logger.debug(`Downloaded file: ${url} -> ${filePath}`); return filePath; } catch (error) { logger.error(`Failed to download file from ${url}:`, error); throw new Error(`Failed to download file: ${error instanceof Error ? error.message : String(error)}`); } } /** * Save Base64 data */ private async saveBase64(base64Data: string): Promise<string> { try { // Parse data URL format const matches = base64Data.match(/^data:([^;]+);base64,(.+)$/); if (!matches) { throw new Error('Invalid base64 data format'); } const [, mimeType, data] = matches; const buffer = Buffer.from(data, 'base64'); const extension = this.getExtensionFromMimeType(mimeType) || '.bin'; const fileName = `base64_${uuidv4()}${extension}`; const filePath = path.join(this.tempDir, fileName); await fs.writeFile(filePath, buffer); logger.debug(`Saved base64 data: ${filePath}`); return filePath; } catch (error) { logger.error('Failed to save base64 data:', error); throw new Error(`Failed to save base64 data: ${error instanceof Error ? error.message : String(error)}`); } } /** * Extract filename from URL */ private getFileNameFromUrl(url: string): string | null { try { const urlObj = new URL(url); const pathname = urlObj.pathname; const fileName = path.basename(pathname); return fileName && fileName !== '/' ? fileName : null; } catch { return null; } } /** * Get file extension from MIME type */ private getExtensionFromMimeType(mimeType: string): string | null { const mimeMap: Record<string, string> = { 'model/gltf+json': GLTF_EXT, 'model/gltf-binary': GLB_EXT, 'application/octet-stream': GLB_EXT, 'application/json': GLTF_EXT }; return mimeMap[mimeType] || null; } /** * Calculate file hash */ async calculateFileHash(filePath: string): Promise<string> { const buffer = await fs.readFile(filePath); return createHash('sha256').update(buffer).digest('hex'); } /** * Get file information */ async getFileInfo(filePath: string) { const stats = await fs.stat(filePath); const hash = await this.calculateFileHash(filePath); return { path: filePath, size: stats.size, hash, created: stats.birthtime, modified: stats.mtime, extension: path.extname(filePath).toLowerCase() }; } /** * Create temporary file path */ createTempPath(extension = '.tmp'): string { const fileName = `${uuidv4()}${extension}`; return path.join(this.tempDir, fileName); } /** * Clean up temporary file */ async cleanup(filePath: string): Promise<void> { try { if (filePath.startsWith(this.tempDir)) { await fs.remove(filePath); logger.debug(`Cleaned up temp file: ${filePath}`); } } catch (error) { logger.warn(`Failed to cleanup file ${filePath}:`, error); } } /** * Clean up all temporary files */ async cleanupAll(): Promise<void> { try { await fs.emptyDir(this.tempDir); logger.info('Cleaned up all temp files'); } catch (error) { logger.error('Failed to cleanup temp directory:', error); } } /** * Copy file */ async copyFile(src: string, dest: string): Promise<void> { await fs.copy(src, dest); logger.debug(`Copied file: ${src} -> ${dest}`); } /** * Ensure directory exists */ async ensureDir(dirPath: string): Promise<void> { await fs.ensureDir(dirPath); } } // Global file handler instance export const globalFileHandler = new FileHandler();

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GeoLibra/3d-asset-processing-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server