Skip to main content
Glama

Targetprocess MCP Server

analyze-attachment.ts11.5 kB
import { z } from 'zod'; import { SemanticOperation, ExecutionContext, OperationResult } from '../../core/interfaces/semantic-operation.interface.js'; import { TPService } from '../../api/client/tp.service.js'; import { logger } from '../../utils/logger.js'; export const analyzeAttachmentSchema = z.object({ attachmentId: z.coerce.number().describe('ID of the attachment to analyze'), includeMetadata: z.boolean().optional().default(true).describe('Include file metadata in analysis') }); export type AnalyzeAttachmentParams = z.infer<typeof analyzeAttachmentSchema>; /** * Secure Attachment Analysis Operation * * Analyzes TargetProcess attachments with security-first approach: * - File type and size validation * - Secure content download * - Model-agnostic output format * - Basic injection prevention * * Security Features: * - MIME type whitelist validation * - File size restrictions (50MB max) * - Suspicious filename detection * - Safe base64 encoding for AI consumption */ export class AnalyzeAttachmentOperation implements SemanticOperation<AnalyzeAttachmentParams> { constructor(private service: TPService) {} get metadata() { return { id: 'analyze-attachment', name: 'Analyze Attachment', description: 'Securely analyze TargetProcess attachments with AI vision support', category: 'analysis', requiredPersonalities: ['default', 'developer', 'tester', 'project-manager', 'product-owner'], examples: [ 'Analyze attachment 12345 for visual content', 'Get secure image data for attachment 67890', 'Analyze screenshot attachment with metadata' ], tags: ['attachment', 'image', 'analysis', 'security'] }; } getSchema() { return analyzeAttachmentSchema; } async execute(context: ExecutionContext, params: AnalyzeAttachmentParams): Promise<OperationResult> { const startTime = Date.now(); try { // Validate input parameters const validatedParams = analyzeAttachmentSchema.parse(params); // Step 1: Get attachment metadata const attachmentInfo = await this.service.getAttachmentInfo(validatedParams.attachmentId); if (!attachmentInfo) { return this.createErrorResult('Attachment not found', validatedParams.attachmentId, startTime); } // Step 2: Security validation const securityCheck = this.validateAttachmentSecurity(attachmentInfo); if (!securityCheck.isValid) { return this.createSecurityErrorResult(securityCheck.reason || 'Security validation failed', attachmentInfo, startTime); } // Step 3: Download and process attachment const processedAttachment = await this.securelyProcessAttachment( validatedParams.attachmentId, attachmentInfo, validatedParams ); // Step 4: Generate analysis result return this.createSuccessResult(processedAttachment, attachmentInfo, validatedParams, startTime); } catch (error) { logger.error('Attachment analysis error:', error); return this.createErrorResult( error instanceof Error ? error.message : 'Unknown error occurred', params.attachmentId, startTime ); } } /** * Validate attachment security before processing */ private validateAttachmentSecurity(attachmentInfo: any): { isValid: boolean; reason?: string } { // Check file size (50MB max) const MAX_FILE_SIZE = 50 * 1024 * 1024; if (attachmentInfo.Size && attachmentInfo.Size > MAX_FILE_SIZE) { return { isValid: false, reason: `File too large: ${Math.round(attachmentInfo.Size / 1024 / 1024)}MB (max: 50MB)` }; } // Check MIME type whitelist const ALLOWED_IMAGE_TYPES = [ 'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/bmp', 'image/svg+xml' ]; const ALLOWED_DOCUMENT_TYPES = [ 'application/pdf', 'text/plain', 'text/csv', 'application/json', 'application/xml' ]; const ALLOWED_TYPES = [...ALLOWED_IMAGE_TYPES, ...ALLOWED_DOCUMENT_TYPES]; if (attachmentInfo.MimeType && !ALLOWED_TYPES.includes(attachmentInfo.MimeType.toLowerCase())) { return { isValid: false, reason: `Unsupported file type: ${attachmentInfo.MimeType}` }; } // Check for suspicious filename patterns const suspiciousPatterns = [ /\.(exe|bat|cmd|sh|ps1|scr|com|pif)$/i, /\.\w+\.\w+$/, // Double extensions /[<>"|*?]/, // Invalid filename characters ]; if (attachmentInfo.Name) { for (const pattern of suspiciousPatterns) { if (pattern.test(attachmentInfo.Name)) { return { isValid: false, reason: 'Suspicious filename detected' }; } } } return { isValid: true }; } /** * Securely process attachment with basic validation */ private async securelyProcessAttachment( attachmentId: number, attachmentInfo: any, params: AnalyzeAttachmentParams ): Promise<any> { // Download attachment securely const downloadResult = await this.service.downloadAttachment(attachmentId); if (!downloadResult.base64Content) { throw new Error('Failed to download attachment content'); } // Determine content type const isImage = this.isImageType(attachmentInfo.MimeType); let processingNotes: string[] = []; // Basic security validation processingNotes.push('✅ File type and size validated'); processingNotes.push('🔒 Content downloaded securely'); // Calculate approximate file size for information const approximateSize = Math.round((downloadResult.base64Content.length * 3) / 4); if (approximateSize > 1024 * 1024) { // 1MB processingNotes.push(`📏 Large file: ~${Math.round(approximateSize/1024/1024)}MB`); } if (isImage) { processingNotes.push('🖼️ Image ready for AI vision analysis'); } else { processingNotes.push('📄 Document content available'); } return { attachmentId, filename: downloadResult.filename, mimeType: downloadResult.mimeType, size: downloadResult.size, base64Content: downloadResult.base64Content, processingNotes, isImage, securityStatus: 'validated', uploadDate: downloadResult.uploadDate, owner: downloadResult.owner }; } /** * Check if MIME type indicates an image */ private isImageType(mimeType?: string): boolean { return !!(mimeType && mimeType.toLowerCase().startsWith('image/')); } /** * Create successful analysis result */ private createSuccessResult( processedAttachment: any, attachmentInfo: any, params: AnalyzeAttachmentParams, startTime: number ): OperationResult { const executionTime = Date.now() - startTime; // Build response content let analysisText = `📊 **Attachment Analysis Complete**\n\n`; // File information analysisText += `📁 **File Information:**\n`; analysisText += `- Name: ${processedAttachment.filename}\n`; analysisText += `- Type: ${processedAttachment.mimeType}\n`; analysisText += `- Size: ${Math.round(processedAttachment.size / 1024)} KB\n`; analysisText += `- Security: ✅ Validated\n\n`; // Processing notes if (processedAttachment.processingNotes.length > 0) { analysisText += `🔧 **Processing:**\n`; processedAttachment.processingNotes.forEach((note: string) => { analysisText += `- ${note}\n`; }); analysisText += '\n'; } // Content analysis if (processedAttachment.isImage) { analysisText += `🖼️ **Image Data Available**\n`; analysisText += `- Format: ${processedAttachment.mimeType}\n`; analysisText += `- Content: Secure base64 data ready for AI analysis\n`; analysisText += `- Processing: Sanitized and validated\n\n`; // Include the actual image data for AI analysis const dataUrl = `data:${processedAttachment.mimeType};base64,${processedAttachment.base64Content}`; analysisText += `![Attachment ${processedAttachment.attachmentId}](${dataUrl})\n\n`; } else { analysisText += `📄 **Document Analysis:**\n`; analysisText += `- Type: ${processedAttachment.mimeType}\n`; analysisText += `- Content: Available for processing\n\n`; } // Build suggestions const suggestions: string[] = []; if (processedAttachment.isImage) { suggestions.push('Describe what you see in this image'); suggestions.push('Analyze the technical content or diagrams shown'); suggestions.push('Extract any text visible in the image'); } else { suggestions.push(`get-entity entityType:${attachmentInfo.General?.ResourceType || 'General'} id:${attachmentInfo.General?.Id} - View entity details`); suggestions.push('search-entities - Find related items'); } return { content: [{ type: 'text', text: analysisText }], metadata: { executionTime, apiCallsCount: 2, // getAttachmentInfo + downloadAttachment cacheHits: 0, attachmentId: processedAttachment.attachmentId, fileType: processedAttachment.mimeType, securityStatus: processedAttachment.securityStatus, isImage: processedAttachment.isImage, processingNotes: processedAttachment.processingNotes } as any, suggestions }; } /** * Create error result */ private createErrorResult(message: string, attachmentId: number, startTime: number): OperationResult { const executionTime = Date.now() - startTime; return { content: [{ type: 'text', text: `❌ **Attachment Analysis Failed**\n\nAttachment ID: ${attachmentId}\nError: ${message}\n\n💡 **Suggestions:**\n- Verify the attachment ID is correct\n- Check if the attachment exists and is accessible\n- Ensure the file type is supported` }], metadata: { executionTime, apiCallsCount: 1, cacheHits: 0, error: message, attachmentId } as any, suggestions: [ 'search-entities - Find entities with attachments', 'get-entity - Check if the parent entity exists' ] }; } /** * Create security error result */ private createSecurityErrorResult(reason: string, attachmentInfo: any, startTime: number): OperationResult { const executionTime = Date.now() - startTime; return { content: [{ type: 'text', text: `🔒 **Security Validation Failed**\n\nAttachment: ${attachmentInfo.Name}\nReason: ${reason}\n\n**File Information:**\n- Type: ${attachmentInfo.MimeType}\n- Size: ${Math.round(attachmentInfo.Size / 1024)} KB\n\n💡 **Security Guidelines:**\n- Only images and safe document types are supported\n- Maximum file size is 50MB\n- Suspicious filenames are blocked\n- All files are scanned for security threats` }], metadata: { executionTime, apiCallsCount: 1, cacheHits: 0, securityReason: reason, attachmentInfo: { id: attachmentInfo.Id, name: attachmentInfo.Name, mimeType: attachmentInfo.MimeType, size: attachmentInfo.Size } } as any, suggestions: [ 'Use a supported file type (JPEG, PNG, PDF, etc.)', 'Reduce file size if it exceeds limits', 'Contact administrator if you need additional file type support' ] }; } }

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/aaronsb/apptio-target-process-mcp'

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