Skip to main content
Glama
marco-looy
by marco-looy
add-case-attachments.js16.7 kB
import { BaseTool } from '../../registry/base-tool.js'; import { getSessionCredentialsSchema } from '../../utils/tool-schema.js'; export class AddCaseAttachmentsTool extends BaseTool { /** * Get the category this tool belongs to */ static getCategory() { return 'attachments'; } /** * Get tool definition for MCP protocol */ static getDefinition() { return { name: 'add_case_attachments', description: 'Attach files and/or URLs to a Pega case regardless of the context or stage of the case lifecycle. Can attach temporary uploaded files using their IDs (from upload_attachment tool), or add URL/link attachments directly. Supports multiple attachments in a single atomic operation - if any attachment fails, no attachments are added to the case.', inputSchema: { type: 'object', properties: { caseID: { type: 'string', description: 'Case ID. Example: "MYORG-APP-WORK C-1001". Complete identifier including spaces."OSIEO3-DOCSAPP-WORK T-561003". a complete case identifier including spaces and special characters.' }, attachments: { type: 'array', description: 'Array of attachment objects to add to the case. Can contain file attachments (using temporary attachment IDs from upload_attachment tool) and/or URL attachments. All attachments must be successfully processed or none will be attached (atomic operation).', items: { type: 'object', properties: { type: { type: 'string', enum: ['File', 'URL'], description: 'Attachment type. "File" for file attachments or "URL" for URL/link attachments.' }, category: { type: 'string', enum: ['File', 'URL'], description: 'Attachment category. Must match the type ("File" or "URL").' }, ID: { type: 'string', description: 'Temporary attachment ID returned from upload_attachment tool (required for File type). Example: "450b7275-8868-43ca-9827-bcfd9ec1b54b". Note: Temporary attachments expire after 2 hours if not linked to a case.' }, url: { type: 'string', description: 'URL/link to attach to the case (required for URL type). Example: "https://www.google.com". a valid URL format.' }, name: { type: 'string', description: 'Display name for the URL attachment (required for URL type). Example: "google". This will be shown as the attachment name in the case.' } }, required: ['type', 'category'] }, minItems: 1, maxItems: 50 }, sessionCredentials: getSessionCredentialsSchema() }, required: ['caseID', 'attachments'] } }; } /** * Execute the add case attachments operation */ async execute(params) { const { caseID, attachments } = params; let sessionInfo = null; try { sessionInfo = this.initializeSessionConfig(params); // Basic parameter validation using base class const requiredValidation = this.validateRequiredParams(params, ['caseID', 'attachments']); if (requiredValidation) { return this.createErrorResponse(`Add Attachments to Case: ${caseID || 'UNKNOWN'}`, { message: requiredValidation.error }, { caseID, attachments }); } // Additional comprehensive parameter validation for complex logic const validationResult = this.validateParameters(caseID, attachments); if (!validationResult.valid) { return this.createErrorResponse(`Add Attachments to Case: ${caseID}`, { message: validationResult.error }, { caseID, attachments }); } // Execute with standardized error handling return await this.executeWithErrorHandling( `Add Attachments to Case: ${caseID}`, async () => await this.pegaClient.addCaseAttachments(caseID, attachments), { caseID, attachments, sessionInfo } ); } catch (error) { return { content: [{ type: 'text', text: `## Error: Add Attachments to Case\n\n**Unexpected Error**: ${error.message}\n\n${sessionInfo ? `**Session**: ${sessionInfo.sessionId} (${sessionInfo.authMode} mode)\n` : ''}*Error occurred at: ${new Date().toISOString()}*` }] }; } } /** * Comprehensive parameter validation */ validateParameters(caseID, attachments) { // Validate caseID if (!caseID || typeof caseID !== 'string' || caseID.trim() === '') { return { valid: false, error: 'Invalid caseID parameter. Case ID must be a non-empty string containing the full case handle.' }; } // Validate attachments array if (!attachments || !Array.isArray(attachments)) { return { valid: false, error: 'Invalid attachments parameter. Attachments must be a non-empty array of attachment objects.' }; } if (attachments.length === 0) { return { valid: false, error: 'Attachments array cannot be empty. Please provide at least one attachment to add to the case.' }; } if (attachments.length > 50) { return { valid: false, error: 'Too many attachments provided. Maximum of 50 attachments can be added in a single operation.' }; } // Validate each attachment object for (let i = 0; i < attachments.length; i++) { const attachment = attachments[i]; const attachmentValidation = this.validateAttachmentObject(attachment, i); if (!attachmentValidation.valid) { return attachmentValidation; } } return { valid: true }; } /** * Validate individual attachment object */ validateAttachmentObject(attachment, index) { if (!attachment || typeof attachment !== 'object') { return { valid: false, error: `Invalid attachment at index ${index}. Each attachment must be an object with required properties.` }; } const { type, category } = attachment; // Validate required type field if (!type || typeof type !== 'string') { return { valid: false, error: `Missing or invalid 'type' field in attachment at index ${index}. Type must be either "File" or "URL".` }; } // Validate required category field if (!category || typeof category !== 'string') { return { valid: false, error: `Missing or invalid 'category' field in attachment at index ${index}. Category must match the type ("File" or "URL").` }; } // Validate File attachment if (type === 'File') { if (category !== 'File') { return { valid: false, error: `Invalid category for File attachment at index ${index}. Category must be "File" when type is "File".` }; } const { ID } = attachment; if (!ID || typeof ID !== 'string' || ID.trim() === '') { return { valid: false, error: `Missing or invalid 'ID' field in File attachment at index ${index}. ID must be a non-empty string containing the temporary attachment ID from upload_attachment tool.` }; } // Check for unexpected properties const allowedProps = ['type', 'category', 'ID']; const extraProps = Object.keys(attachment).filter(prop => !allowedProps.includes(prop)); if (extraProps.length > 0) { return { valid: false, error: `Unexpected properties in File attachment at index ${index}: ${extraProps.join(', ')}. File attachments only support: type, category, ID.` }; } } // Validate URL attachment else if (type === 'URL') { if (category !== 'URL') { return { valid: false, error: `Invalid category for URL attachment at index ${index}. Category must be "URL" when type is "URL".` }; } const { url, name } = attachment; if (!url || typeof url !== 'string' || url.trim() === '') { return { valid: false, error: `Missing or invalid 'url' field in URL attachment at index ${index}. URL must be a non-empty string containing a valid URL.` }; } // Basic URL format validation try { new URL(url.trim()); } catch (urlError) { return { valid: false, error: `Invalid URL format in URL attachment at index ${index}: "${url}". Please provide a valid URL (Example: "https://www.example.com").` }; } if (!name || typeof name !== 'string' || name.trim() === '') { return { valid: false, error: `Missing or invalid 'name' field in URL attachment at index ${index}. Name must be a non-empty string for the display name of the URL attachment.` }; } // Check for unexpected properties const allowedProps = ['type', 'category', 'url', 'name']; const extraProps = Object.keys(attachment).filter(prop => !allowedProps.includes(prop)); if (extraProps.length > 0) { return { valid: false, error: `Unexpected properties in URL attachment at index ${index}: ${extraProps.join(', ')}. URL attachments only support: type, category, url, name.` }; } } // Invalid type else { return { valid: false, error: `Invalid attachment type "${type}" at index ${index}. Type must be either "File" or "URL".` }; } return { valid: true }; } /** * Override formatSuccessResponse to add case attachments specific formatting */ formatSuccessResponse(operation, data, options = {}) { const { caseID, attachments, sessionInfo } = options; let response = `## ${operation}\n\n`; response += `*Operation completed at: ${new Date().toISOString()}*\n\n`; if (sessionInfo) { response += `### Session Information\n`; response += `- **Session ID**: ${sessionInfo.sessionId}\n`; response += `- **Authentication Mode**: ${sessionInfo.authMode.toUpperCase()}\n`; response += `- **Configuration Source**: ${sessionInfo.configSource}\n\n`; } response += `Successfully attached ${attachments.length} ${attachments.length === 1 ? 'item' : 'items'} to the case.\n\n`; // Display attachment summary response += '### Attachment Summary\n'; const fileAttachments = attachments.filter(att => att.type === 'File'); const urlAttachments = attachments.filter(att => att.type === 'URL'); if (fileAttachments.length > 0) { response += `- **Files Attached**: ${fileAttachments.length}\n`; fileAttachments.forEach((att, index) => { response += ` ${index + 1}. Temporary Attachment ID: ${att.ID}\n`; }); } if (urlAttachments.length > 0) { response += `- **URLs Attached**: ${urlAttachments.length}\n`; urlAttachments.forEach((att, index) => { response += ` ${index + 1}. ${att.name}: ${att.url}\n`; }); } // Display operation details response += '\n### Operation Details\n'; response += '- **Atomic Operation**: All attachments were successfully processed\n'; response += '- **Status**: All attachments are now permanently linked to the case\n'; if (fileAttachments.length > 0) { response += '- **File Attachments**: Temporary attachments have been converted to permanent case attachments\n'; } // Display next steps response += '\n### Next Steps\n'; response += '- Use `get_case_attachments` tool to retrieve all attachments for this case\n'; response += '- Use `get_case` tool to view the updated case with attachment information\n'; response += '- Attachments are now available to all users with case access\n'; return response; } /** * Override formatErrorResponse to handle add_case_attachments specific formatting */ formatErrorResponse(operation, error, options = {}) { const { caseID, attachments } = options; let response = `## ${operation}\n\n`; if (caseID) { response += `**Case ID**: ${caseID}\n`; } if (attachments && Array.isArray(attachments)) { response += `**Attempted Attachments**: ${attachments.length}\n`; } response += `**Error Type**: ${error.type}\n`; response += `**Message**: ${error.message}\n`; if (error.details) { response += `**Details**: ${error.details}\n`; } if (error.status) { response += `**HTTP Status**: ${error.status} ${error.statusText}\n`; } // Add specific guidance based on error type switch (error.type) { case 'BAD_REQUEST': response += '\n**Common Causes & Solutions**:\n'; if (error.errorDetails && error.errorDetails.some(detail => detail.message === 'Error_File_Not_Found')) { response += '- **Temporary Attachment Not Found**: One or more temporary attachment IDs are invalid or have expired\n'; response += ' - Temporary attachments expire after 2 hours if not linked to a case\n'; response += ' - Use `upload_attachment` tool to create new temporary attachments\n'; response += ' - Verify the attachment IDs are copied correctly from upload responses\n'; } else { response += '- **Invalid Request Format**: Check that all attachment objects have required fields\n'; response += '- **File Attachments**: Ensure type="File", category="File", and valid ID\n'; response += '- **URL Attachments**: Ensure type="URL", category="URL", valid url, and name\n'; response += '- **Mixed Attachments**: Verify each attachment follows its type-specific format\n'; } break; case 'UNAUTHORIZED': response += '\n**Solution**: Authentication token may have expired. The system will attempt to refresh the token on the next request.\n'; break; case 'FORBIDDEN': response += '\n**Solutions**:\n'; response += '- Verify you have permission to update this case\n'; response += '- Check if the case is in a stage that allows attachments\n'; response += '- Contact your system administrator for case access permissions\n'; break; case 'NOT_FOUND': response += '\n**Solutions**:\n'; response += '- Verify the case ID is correct and includes the full case handle\n'; response += '- Check if you have access to view/update this case\n'; response += '- Ensure the case exists and is not deleted\n'; response += '- Use `get_case` tool to verify case accessibility\n'; break; case 'INTERNAL_SERVER_ERROR': response += '\n**Solutions**:\n'; response += '- This is a server-side error in the Pega system\n'; response += '- Try the operation again after a brief delay\n'; response += '- Contact your system administrator if the error persists\n'; response += '- Check Pega system logs for detailed error information\n'; break; case 'CONNECTION_ERROR': response += '\n**Solution**: Verify the Pega instance URL and network connectivity.\n'; break; } // Display detailed error information if available if (error.errorDetails && error.errorDetails.length > 0) { response += '\n### Detailed Error Information\n'; error.errorDetails.forEach((detail, index) => { response += `${index + 1}. **${detail.message || 'Error'}**: ${detail.localizedValue || detail.message}\n`; if (detail.messageParameters && detail.messageParameters.length > 0) { response += ` - Parameters: ${detail.messageParameters.join(', ')}\n`; } }); } // Display attachment details that failed if (attachments && Array.isArray(attachments) && attachments.length > 0) { response += '\n### Attachment Details (Failed to Process)\n'; attachments.forEach((att, index) => { if (att.type === 'File') { response += `${index + 1}. **File**: Temporary ID ${att.ID}\n`; } else if (att.type === 'URL') { response += `${index + 1}. **URL**: ${att.name} (${att.url})\n`; } }); } response += '\n**Important**: Due to atomic operation behavior, NO attachments were added to the case since at least one failed.\n'; response += '\n---\n'; response += `*Error occurred at: ${new Date().toISOString()}*`; return response; } }

Latest Blog Posts

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/marco-looy/pega-dx-mcp'

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