add_attachment_to_case
Upload file attachments to TestRail test cases to provide supporting documentation, evidence, or reference materials for comprehensive test management.
Instructions
Upload a file attachment to a TestRail test case.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| case_id | Yes | TestRail case ID | |
| file_path | Yes | Path to the file to upload as attachment |
Implementation Reference
- src/server.ts:479-510 (handler)MCP tool handler function that executes the add_attachment_to_case logic: logs input, calls service function, returns JSON response or formatted error.async ({ case_id, file_path }) => { logger.debug(`Add attachment tool called with case_id: ${case_id}, file_path: ${file_path}`); try { const result = await addAttachmentToCase(case_id, file_path); logger.debug(`Add attachment tool completed successfully for case_id: ${case_id}, attachment_id: ${result.attachment_id}`); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (err) { logger.error({ err }, `Add attachment tool failed for case_id: ${case_id}, file_path: ${file_path}`); const e = err as { type?: string; status?: number; message?: string }; let message = 'Unexpected error'; if (e?.type === 'auth') message = 'Authentication failed: check TESTRAIL_USER/API_KEY'; else if (e?.type === 'not_found') message = `Case ${case_id} not found`; else if (e?.type === 'rate_limited') message = 'Rate limited by TestRail; try again later'; else if (e?.type === 'server') message = 'TestRail server error'; else if (e?.type === 'network') message = 'Network error contacting TestRail'; else if (e?.message) message = e.message; return { content: [ { type: 'text', text: message }, ], isError: true, }; } },
- src/server.ts:471-477 (schema)Zod input schema defining parameters: case_id (positive integer), file_path (non-empty string). Title and description for the tool.{ title: 'Add Attachment to TestRail Case', description: 'Upload a file attachment to a TestRail test case.', inputSchema: { case_id: z.number().int().positive().describe('TestRail case ID'), file_path: z.string().min(1).describe('Path to the file to upload as attachment'), },
- src/server.ts:469-511 (registration)Registers the 'add_attachment_to_case' tool with MCP server using schema and handler.server.registerTool( 'add_attachment_to_case', { title: 'Add Attachment to TestRail Case', description: 'Upload a file attachment to a TestRail test case.', inputSchema: { case_id: z.number().int().positive().describe('TestRail case ID'), file_path: z.string().min(1).describe('Path to the file to upload as attachment'), }, }, async ({ case_id, file_path }) => { logger.debug(`Add attachment tool called with case_id: ${case_id}, file_path: ${file_path}`); try { const result = await addAttachmentToCase(case_id, file_path); logger.debug(`Add attachment tool completed successfully for case_id: ${case_id}, attachment_id: ${result.attachment_id}`); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (err) { logger.error({ err }, `Add attachment tool failed for case_id: ${case_id}, file_path: ${file_path}`); const e = err as { type?: string; status?: number; message?: string }; let message = 'Unexpected error'; if (e?.type === 'auth') message = 'Authentication failed: check TESTRAIL_USER/API_KEY'; else if (e?.type === 'not_found') message = `Case ${case_id} not found`; else if (e?.type === 'rate_limited') message = 'Rate limited by TestRail; try again later'; else if (e?.type === 'server') message = 'TestRail server error'; else if (e?.type === 'network') message = 'Network error contacting TestRail'; else if (e?.message) message = e.message; return { content: [ { type: 'text', text: message }, ], isError: true, }; } }, );
- Service layer wrapper that calls TestRail client and normalizes response to {attachment_id}.export async function addAttachmentToCase(caseId: number, filePath: string): Promise<AttachmentResponse> { const response: TestRailAttachmentResponse = await testRailClient.addAttachmentToCase(caseId, filePath); return { attachment_id: response.attachment_id, }; }
- TestRailClient method that performs multipart file upload to TestRail API endpoint /add_attachment_to_case/{caseId} using FormData and axios.async addAttachmentToCase(caseId: number, filePath: string): Promise<TestRailAttachmentResponse> { try { // Import FormData dynamically to avoid issues in Node.js environment const FormData = (await import('form-data')).default; const fs = await import('fs'); const formData = new FormData(); const fileStream = fs.createReadStream(filePath); const fileName = filePath.split(/[/\\]/).pop() || 'attachment'; formData.append('attachment', fileStream, fileName); // Create a new axios instance for multipart upload const uploadClient = axios.create({ baseURL: `${config.TESTRAIL_URL}/index.php?/api/v2`, timeout: config.TESTRAIL_TIMEOUT_MS, auth: { username: config.TESTRAIL_USERNAME, password: config.TESTRAIL_API_KEY, }, headers: { ...formData.getHeaders(), }, validateStatus: () => true, }); const res = await uploadClient.post(`/add_attachment_to_case/${caseId}`, formData); if (res.status >= 200 && res.status < 300) { return res.data as TestRailAttachmentResponse; } throw Object.assign(new Error(`HTTP ${res.status}`), { response: res }); } catch (err) { const normalized = this.normalizeError(err); const safeDetails = this.getSafeErrorDetails(err); logger.error({ err: normalized, details: safeDetails, caseId, filePath }, 'TestRail addAttachmentToCase failed'); throw normalized; } }