@z_ai/mcp-server
Version:
MCP Server for Z.AI - A Model Context Protocol server that provides AI capabilities
127 lines (126 loc) • 4.63 kB
JavaScript
import * as fs from 'fs';
import * as path from 'path';
import { FileNotFoundError, ValidationError } from '../types/index.js';
/**
* File operations service
*/
export class FileService {
/**
* Check if a string is a URL
*/
static isUrl(source) {
try {
const url = new URL(source);
return url.protocol === 'http:' || url.protocol === 'https:';
}
catch {
return false;
}
}
/**
* Validate if image source exists and check size limit
* @param imageSource Path to image file or URL
* @param maxSizeMB Maximum file size in MB (default: 5MB)
*/
static async validateImageSource(imageSource, maxSizeMB = 5) {
if (this.isUrl(imageSource)) {
return;
}
if (!fs.existsSync(imageSource)) {
throw new FileNotFoundError(`Image file not found: ${imageSource}`);
}
const stats = fs.statSync(imageSource);
const maxSizeBytes = maxSizeMB * 1024 * 1024;
if (stats.size > maxSizeBytes) {
throw new ValidationError(`Image file too large: ${(stats.size / (1024 * 1024)).toFixed(2)}MB. Maximum allowed: ${maxSizeMB}MB`);
}
const ext = path.extname(imageSource).toLowerCase();
const supportedExts = ['.jpg', '.jpeg', '.png'];
if (!supportedExts.includes(ext)) {
throw new ValidationError(`Unsupported image format: ${ext}. Supported formats: ${supportedExts.join(', ')}`);
}
}
/**
* Validate if video source exists and check size limit
* @param videoSource Path to video file or URL
* @param maxSizeMB Maximum file size in MB (default: 8MB)
*/
static async validateVideoSource(videoSource, maxSizeMB = 8) {
if (this.isUrl(videoSource)) {
return;
}
if (!fs.existsSync(videoSource)) {
throw new FileNotFoundError(videoSource);
}
const stats = fs.statSync(videoSource);
const fileSizeMB = stats.size / (1024 * 1024);
if (fileSizeMB > maxSizeMB) {
throw new Error(`Video file size (${fileSizeMB.toFixed(2)}MB) exceeds maximum allowed size (${maxSizeMB}MB)`);
}
}
/**
* Encode image to base64 data URL (from file or URL)
* @param imageSource Path to image file or URL
* @returns Base64 encoded image data or URL
*/
static async encodeImageToBase64(imageSource) {
if (this.isUrl(imageSource)) {
// For URLs, return the URL directly (no base64 encoding needed)
return imageSource;
}
// For local files, encode to base64
const imageBuffer = fs.readFileSync(imageSource);
const ext = path.extname(imageSource).toLowerCase().slice(1);
const mimeType = this.getMimeType(ext);
console.debug('Encoded image to base64', { imageSource, mimeType });
return `data:${mimeType};base64,${imageBuffer.toString('base64')}`;
}
/**
* Encode video to base64 data URL (from file or URL)
* @param videoSource Path to video file or URL
* @returns Base64 encoded video data URL
*/
static async encodeVideoToBase64(videoSource) {
if (this.isUrl(videoSource)) {
// For URLs, return the URL directly (no base64 encoding needed)
return videoSource;
}
// For local files, encode to base64
const videoBuffer = fs.readFileSync(videoSource);
const ext = path.extname(videoSource).toLowerCase().slice(1);
const mimeType = this.getVideoMimeType(ext);
console.debug('Encoded video to base64', { videoSource, mimeType });
return `data:${mimeType};base64,${videoBuffer.toString('base64')}`;
}
/**
* Get MIME type for image file extension
*/
static getMimeType(extension) {
const mimeTypes = {
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg'
};
return mimeTypes[extension] || 'image/png';
}
/**
* Get MIME type for video file extension
* @param extension File extension without dot
* @returns MIME type string
*/
static getVideoMimeType(extension) {
const mimeTypes = {
mp4: 'video/mp4',
avi: 'video/x-msvideo',
mov: 'video/quicktime',
wmv: 'video/x-ms-wmv',
webm: 'video/webm',
m4v: 'video/x-m4v'
};
return mimeTypes[extension] || 'video/mp4';
}
}
/**
* File service for image operations
*/
export const fileService = FileService;