Skip to main content
Glama

Chicken Business Management MCP Server

by PSYGER02
filesystem-tools.ts20.6 kB
/** * Filesystem MCP Tools Implementation * Secure file operations with configurable access controls for business data */ import { z } from 'zod'; import fs from 'fs/promises'; import path from 'path'; import { createReadStream, createWriteStream } from 'fs'; import { pipeline } from 'stream/promises'; export class FilesystemMCPTools { private allowedBasePaths: string[]; private readOnlyMode: boolean; private maxFileSize: number; constructor() { // Configure allowed directories for security this.allowedBasePaths = [ path.resolve('./data'), path.resolve('./uploads'), path.resolve('./reports'), path.resolve('./exports'), path.resolve('./backups') ]; this.readOnlyMode = process.env.FILESYSTEM_READONLY === 'true'; this.maxFileSize = parseInt(process.env.MAX_FILE_SIZE || '50000000'); // 50MB default } /** * Read file content with business context awareness */ async filesystem_read(args: { path: string; encoding?: string; maxSize?: number; }) { const fullPath = await this.validateAndResolvePath(args.path); try { // Check file size before reading const stats = await fs.stat(fullPath); const maxSize = args.maxSize || this.maxFileSize; if (stats.size > maxSize) { throw new Error(`File size ${stats.size} exceeds maximum ${maxSize} bytes`); } const content = await fs.readFile(fullPath, args.encoding || 'utf-8'); // Analyze content for business relevance const businessMetadata = await this.analyzeFileForBusiness(content, fullPath); return { path: args.path, content, metadata: { size: stats.size, modified: stats.mtime, created: stats.birthtime, encoding: args.encoding || 'utf-8', isBusinessData: businessMetadata.isBusinessData, dataType: businessMetadata.dataType, extractedInfo: businessMetadata.extractedInfo } }; } catch (error) { throw new Error(`Failed to read file ${args.path}: ${error instanceof Error ? error.message : String(error)}`); } } /** * Write file content with business validation */ async filesystem_write(args: { path: string; content: string; encoding?: string; createDirs?: boolean; businessContext?: string; }) { if (this.readOnlyMode) { throw new Error('Filesystem is in read-only mode'); } const fullPath = await this.validateAndResolvePath(args.path); try { // Validate content size const contentSize = Buffer.byteLength(args.content, args.encoding || 'utf-8'); if (contentSize > this.maxFileSize) { throw new Error(`Content size ${contentSize} exceeds maximum ${this.maxFileSize} bytes`); } // Create directories if requested if (args.createDirs) { await fs.mkdir(path.dirname(fullPath), { recursive: true }); } // Validate business content if context provided if (args.businessContext) { const validation = await this.validateBusinessContent(args.content, args.businessContext); if (!validation.isValid) { throw new Error(`Business validation failed: ${validation.errors.join(', ')}`); } } await fs.writeFile(fullPath, args.content, args.encoding || 'utf-8'); const stats = await fs.stat(fullPath); return { path: args.path, size: stats.size, written: true, timestamp: new Date(), businessContext: args.businessContext, metadata: { encoding: args.encoding || 'utf-8', created: stats.birthtime, modified: stats.mtime } }; } catch (error) { throw new Error(`Failed to write file ${args.path}: ${error instanceof Error ? error.message : String(error)}`); } } /** * List directory contents with business file classification */ async filesystem_list(args: { path: string; recursive?: boolean; includeHidden?: boolean; businessFilter?: string; }) { const fullPath = await this.validateAndResolvePath(args.path); try { const entries = await this.listDirectoryRecursive( fullPath, args.recursive || false, args.includeHidden || false ); // Classify business files const classifiedEntries = await Promise.all( entries.map(async (entry) => { const businessInfo = await this.classifyBusinessFile(entry); return { ...entry, businessInfo }; }) ); // Filter by business context if specified let filteredEntries = classifiedEntries; if (args.businessFilter) { filteredEntries = classifiedEntries.filter(entry => entry.businessInfo.category === args.businessFilter || entry.businessInfo.tags.includes(args.businessFilter) ); } return { path: args.path, entries: filteredEntries, summary: { total_files: filteredEntries.filter(e => e.type === 'file').length, total_directories: filteredEntries.filter(e => e.type === 'directory').length, business_files: filteredEntries.filter(e => e.businessInfo.isBusinessData).length, categories: this.summarizeBusinessCategories(filteredEntries) } }; } catch (error) { throw new Error(`Failed to list directory ${args.path}: ${error instanceof Error ? error.message : String(error)}`); } } /** * Delete file or directory with business impact analysis */ async filesystem_delete(args: { path: string; recursive?: boolean; analyzeImpact?: boolean; }) { if (this.readOnlyMode) { throw new Error('Filesystem is in read-only mode'); } const fullPath = await this.validateAndResolvePath(args.path); try { const stats = await fs.stat(fullPath); let impactAnalysis = null; // Analyze business impact before deletion if (args.analyzeImpact) { impactAnalysis = await this.analyzeDeleteImpact(fullPath, stats.isDirectory()); } if (stats.isDirectory()) { if (!args.recursive) { // Check if directory is empty const entries = await fs.readdir(fullPath); if (entries.length > 0) { throw new Error('Directory is not empty. Use recursive=true to delete non-empty directories.'); } } await fs.rm(fullPath, { recursive: args.recursive || false, force: true }); } else { await fs.unlink(fullPath); } return { path: args.path, deleted: true, type: stats.isDirectory() ? 'directory' : 'file', timestamp: new Date(), impactAnalysis, size_freed: stats.isFile() ? stats.size : null }; } catch (error) { throw new Error(`Failed to delete ${args.path}: ${error instanceof Error ? error.message : String(error)}`); } } /** * Copy file with business metadata preservation */ async filesystem_copy(args: { sourcePath: string; destinationPath: string; preserveMetadata?: boolean; businessContext?: string; }) { if (this.readOnlyMode) { throw new Error('Filesystem is in read-only mode'); } const sourceFullPath = await this.validateAndResolvePath(args.sourcePath); const destFullPath = await this.validateAndResolvePath(args.destinationPath); try { const sourceStats = await fs.stat(sourceFullPath); if (sourceStats.isDirectory()) { throw new Error('Directory copying not yet implemented. Use individual file operations.'); } // Create destination directory if needed await fs.mkdir(path.dirname(destFullPath), { recursive: true }); // Copy file content await pipeline( createReadStream(sourceFullPath), createWriteStream(destFullPath) ); // Preserve metadata if requested if (args.preserveMetadata) { await fs.utimes(destFullPath, sourceStats.atime, sourceStats.mtime); } const destStats = await fs.stat(destFullPath); return { sourcePath: args.sourcePath, destinationPath: args.destinationPath, copied: true, size: destStats.size, timestamp: new Date(), businessContext: args.businessContext, metadata_preserved: args.preserveMetadata || false }; } catch (error) { throw new Error(`Failed to copy ${args.sourcePath} to ${args.destinationPath}: ${error instanceof Error ? error.message : String(error)}`); } } /** * Search files by content with business intelligence */ async filesystem_search(args: { path: string; pattern: string; fileTypes?: string[]; businessContext?: string; maxResults?: number; }) { const fullPath = await this.validateAndResolvePath(args.path); try { const searchResults = await this.searchFiles( fullPath, args.pattern, args.fileTypes || [], args.maxResults || 50 ); // Enhance results with business intelligence const enhancedResults = await Promise.all( searchResults.map(async (result) => { const businessRelevance = await this.assessBusinessRelevance( result.content, args.businessContext || '' ); return { ...result, businessRelevance }; }) ); // Sort by business relevance if context provided if (args.businessContext) { enhancedResults.sort((a, b) => b.businessRelevance.score - a.businessRelevance.score); } return { pattern: args.pattern, searchPath: args.path, results: enhancedResults, summary: { total_matches: enhancedResults.length, high_relevance: enhancedResults.filter(r => r.businessRelevance.score > 0.8).length, file_types: [...new Set(enhancedResults.map(r => r.fileType))] } }; } catch (error) { throw new Error(`Search failed in ${args.path}: ${error instanceof Error ? error.message : String(error)}`); } } /** * Private helper methods */ private async validateAndResolvePath(inputPath: string): Promise<string> { const resolvedPath = path.resolve(inputPath); // Check if path is within allowed directories const isAllowed = this.allowedBasePaths.some(allowedPath => resolvedPath.startsWith(allowedPath) ); if (!isAllowed) { throw new Error(`Access denied: Path ${inputPath} is not in allowed directories`); } return resolvedPath; } private async listDirectoryRecursive( dirPath: string, recursive: boolean, includeHidden: boolean ): Promise<Array<{ name: string; path: string; type: 'file' | 'directory'; size: number | null; modified: Date; created: Date; }>> { const entries = await fs.readdir(dirPath, { withFileTypes: true }); const results = []; for (const entry of entries) { // Skip hidden files unless requested if (!includeHidden && entry.name.startsWith('.')) { continue; } const entryPath = path.join(dirPath, entry.name); const stats = await fs.stat(entryPath); const entryInfo = { name: entry.name, path: path.relative(process.cwd(), entryPath), type: entry.isDirectory() ? 'directory' as const : 'file' as const, size: entry.isFile() ? stats.size : null, modified: stats.mtime, created: stats.birthtime }; results.push(entryInfo); // Recurse into subdirectories if requested if (recursive && entry.isDirectory()) { const subEntries = await this.listDirectoryRecursive(entryPath, true, includeHidden); results.push(...subEntries); } } return results; } private async analyzeFileForBusiness(content: string, filePath: string): Promise<{ isBusinessData: boolean; dataType: string; extractedInfo: any; }> { const fileName = path.basename(filePath).toLowerCase(); const fileExt = path.extname(filePath).toLowerCase(); // Simple business data detection const businessKeywords = [ 'chicken', 'poultry', 'sales', 'purchase', 'stock', 'inventory', 'supplier', 'customer', 'revenue', 'expense', 'profit', 'forecast' ]; const isBusinessData = businessKeywords.some(keyword => fileName.includes(keyword) || content.toLowerCase().includes(keyword) ); let dataType = 'unknown'; if (fileExt === '.json') dataType = 'json_data'; else if (fileExt === '.csv') dataType = 'csv_data'; else if (fileExt === '.txt') dataType = 'text_notes'; else if (fileExt === '.md') dataType = 'markdown_docs'; return { isBusinessData, dataType, extractedInfo: { keywords_found: businessKeywords.filter(keyword => content.toLowerCase().includes(keyword) ), file_extension: fileExt, content_length: content.length } }; } private async validateBusinessContent(content: string, businessContext: string): Promise<{ isValid: boolean; errors: string[]; }> { const errors = []; // Basic validation rules if (content.length === 0) { errors.push('Content cannot be empty'); } if (businessContext === 'financial_data') { // Validate financial data format if (!content.includes('amount') && !content.includes('price') && !content.includes('cost')) { errors.push('Financial data should contain amount, price, or cost information'); } } if (businessContext === 'inventory_data') { // Validate inventory data format if (!content.includes('quantity') && !content.includes('stock') && !content.includes('items')) { errors.push('Inventory data should contain quantity, stock, or items information'); } } return { isValid: errors.length === 0, errors }; } private async classifyBusinessFile(entry: any): Promise<{ isBusinessData: boolean; category: string; tags: string[]; confidence: number; }> { const fileName = entry.name.toLowerCase(); // Business file classification const classifications = { sales: ['sales', 'revenue', 'income'], inventory: ['stock', 'inventory', 'items'], financial: ['finance', 'expense', 'cost', 'profit'], reports: ['report', 'summary', 'analysis'], notes: ['notes', 'memo', 'log'] }; let category = 'general'; const tags = []; let confidence = 0; for (const [cat, keywords] of Object.entries(classifications)) { const matches = keywords.filter(keyword => fileName.includes(keyword)); if (matches.length > 0) { category = cat; tags.push(...matches); confidence = matches.length / keywords.length; break; } } return { isBusinessData: confidence > 0, category, tags, confidence }; } private async analyzeDeleteImpact(filePath: string, isDirectory: boolean): Promise<{ impactLevel: 'low' | 'medium' | 'high'; warnings: string[]; affectedSystems: string[]; }> { const warnings = []; const affectedSystems = []; let impactLevel: 'low' | 'medium' | 'high' = 'low'; const fileName = path.basename(filePath).toLowerCase(); // Check for important business files if (fileName.includes('backup') || fileName.includes('export')) { warnings.push('This appears to be a backup or export file'); impactLevel = 'medium'; } if (fileName.includes('config') || fileName.includes('settings')) { warnings.push('This appears to be a configuration file'); affectedSystems.push('system_configuration'); impactLevel = 'high'; } if (isDirectory && fileName.includes('data')) { warnings.push('This directory may contain important business data'); affectedSystems.push('business_data'); impactLevel = 'high'; } return { impactLevel, warnings, affectedSystems }; } private summarizeBusinessCategories(entries: any[]): Record<string, number> { return entries .filter(entry => entry.businessInfo.isBusinessData) .reduce((acc, entry) => { const category = entry.businessInfo.category; acc[category] = (acc[category] || 0) + 1; return acc; }, {}); } private async searchFiles( dirPath: string, pattern: string, fileTypes: string[], maxResults: number ): Promise<Array<{ file: string; matches: Array<{ line: number; content: string; }>; content: string; fileType: string; }>> { const results = []; const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { if (results.length >= maxResults) break; const entryPath = path.join(dirPath, entry.name); if (entry.isFile()) { const ext = path.extname(entry.name); // Filter by file type if specified if (fileTypes.length > 0 && !fileTypes.includes(ext)) { continue; } try { const content = await fs.readFile(entryPath, 'utf-8'); const lines = content.split('\n'); const matches = []; lines.forEach((line, index) => { if (line.toLowerCase().includes(pattern.toLowerCase())) { matches.push({ line: index + 1, content: line.trim() }); } }); if (matches.length > 0) { results.push({ file: path.relative(process.cwd(), entryPath), matches, content: content.substring(0, 1000), // First 1KB for context fileType: ext || 'no_extension' }); } } catch (error) { // Skip files that can't be read as text continue; } } } return results; } private async assessBusinessRelevance(content: string, businessContext: string): Promise<{ score: number; reasons: string[]; }> { const reasons = []; let score = 0; if (businessContext) { const contextWords = businessContext.toLowerCase().split(/\s+/); const contentLower = content.toLowerCase(); const matches = contextWords.filter(word => contentLower.includes(word)); score = matches.length / contextWords.length; if (matches.length > 0) { reasons.push(`Matches business context: ${matches.join(', ')}`); } } // Business keyword relevance const businessKeywords = ['chicken', 'sales', 'inventory', 'profit', 'customer']; const businessMatches = businessKeywords.filter(keyword => content.toLowerCase().includes(keyword) ); if (businessMatches.length > 0) { score += businessMatches.length * 0.1; reasons.push(`Contains business keywords: ${businessMatches.join(', ')}`); } return { score: Math.min(score, 1.0), // Cap at 1.0 reasons }; } } // Export singleton instance export const filesystemMCPTools = new FilesystemMCPTools(); // Export schemas for validation export const filesystemSchemas = { filesystem_read: z.object({ path: z.string(), encoding: z.string().optional(), maxSize: z.number().optional() }), filesystem_write: z.object({ path: z.string(), content: z.string(), encoding: z.string().optional(), createDirs: z.boolean().optional(), businessContext: z.string().optional() }), filesystem_list: z.object({ path: z.string(), recursive: z.boolean().optional(), includeHidden: z.boolean().optional(), businessFilter: z.string().optional() }), filesystem_delete: z.object({ path: z.string(), recursive: z.boolean().optional(), analyzeImpact: z.boolean().optional() }), filesystem_copy: z.object({ sourcePath: z.string(), destinationPath: z.string(), preserveMetadata: z.boolean().optional(), businessContext: z.string().optional() }), filesystem_search: z.object({ path: z.string(), pattern: z.string(), fileTypes: z.array(z.string()).optional(), businessContext: z.string().optional(), maxResults: z.number().optional() }) };

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/PSYGER02/mcpserver'

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