Skip to main content
Glama
Derrbal
by Derrbal

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
NameRequiredDescriptionDefault
case_idYesTestRail case ID
file_pathYesPath to the file to upload as attachment

Implementation Reference

  • 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, }; } },
  • 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; } }

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/Derrbal/testrail-mcp'

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