Skip to main content
Glama
Derrbal

TestRail MCP Server

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