Skip to main content
Glama
Derrbal

TestRail MCP Server

by Derrbal

add_case

Create new test cases in TestRail by specifying title, section, type, priority, and custom fields to organize testing requirements systematically.

Instructions

Create a new TestRail test case in a specific section. IMPORTANT: Before creating a case, gather required information using get_projects, get_suites, get_sections, and get_case_fields tools to ensure proper section_id, type_id, and custom field values. Or ask the user to provide the information if not provided.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
titleYesTest case title - should be descriptive and unique within the section
section_idYesSection ID where the case will be created. REQUIRED: Use get_sections tool first to find valid section IDs for your project/suite. Different projects have different section structures.
type_idNoTest case type ID (e.g., 1=Acceptance, 2=Accessibility, 3=Automated, 4=Compatibility, 5=Destructive, 6=Functional, 7=Other, 8=Performance, 9=Regression, 10=Security, 11=Smoke & Sanity, 12=Usability). RECOMMENDED: Use get_cases tool to see what type_id values are used in existing cases in your target section.
priority_idNoPriority ID (1=Low, 2=Medium, 3=High, 4=Critical). RECOMMENDED: Use get_cases tool to see what priority_id values are used in existing cases.
refsNoReferences (e.g., requirement IDs, JIRA tickets, user story numbers). Can be comma-separated for multiple references.
customNoCustom fields (key-value pairs). REQUIRED: Use get_case_fields tool first to discover available custom fields and their valid values. Common fields include: custom_automation_type (1=None, 2=Playwright, 3=ChatGPT, 4=Non-Automated, 5=Partial), custom_environment (1=UAT Only, 2=UAT/Prod, 3=Demo UAT, 4=Live UAT), custom_preconds (preconditions text), custom_steps (test steps text), custom_expected (expected results text). Some custom fields are required by the project configuration.

Implementation Reference

  • Inline handler function for the 'add_case' MCP tool. Validates inputs via Zod schema, constructs CaseCreatePayload, calls addCase service, formats success/error responses.
    server.registerTool(
      'add_case',
      {
        title: 'Add TestRail Case',
        description: 'Create a new TestRail test case in a specific section. IMPORTANT: Before creating a case, gather required information using get_projects, get_suites, get_sections, and get_case_fields tools to ensure proper section_id, type_id, and custom field values. Or ask the user to provide the information if not provided.',
        inputSchema: {
          title: z.string().min(1).describe('Test case title - should be descriptive and unique within the section'),
          section_id: z.number().int().positive().describe('Section ID where the case will be created. REQUIRED: Use get_sections tool first to find valid section IDs for your project/suite. Different projects have different section structures.'),
          type_id: z.number().int().positive().optional().describe('Test case type ID (e.g., 1=Acceptance, 2=Accessibility, 3=Automated, 4=Compatibility, 5=Destructive, 6=Functional, 7=Other, 8=Performance, 9=Regression, 10=Security, 11=Smoke & Sanity, 12=Usability). RECOMMENDED: Use get_cases tool to see what type_id values are used in existing cases in your target section.'),
          priority_id: z.number().int().positive().optional().describe('Priority ID (1=Low, 2=Medium, 3=High, 4=Critical). RECOMMENDED: Use get_cases tool to see what priority_id values are used in existing cases.'),
          refs: z.string().nullable().optional().describe('References (e.g., requirement IDs, JIRA tickets, user story numbers). Can be comma-separated for multiple references.'),
          custom: z.record(z.string(), z.unknown()).optional().describe('Custom fields (key-value pairs). REQUIRED: Use get_case_fields tool first to discover available custom fields and their valid values. Common fields include: custom_automation_type (1=None, 2=Playwright, 3=ChatGPT, 4=Non-Automated, 5=Partial), custom_environment (1=UAT Only, 2=UAT/Prod, 3=Demo UAT, 4=Live UAT), custom_preconds (preconditions text), custom_steps (test steps text), custom_expected (expected results text). Some custom fields are required by the project configuration.'),
        },
      },
      async ({ title, section_id, type_id, priority_id, refs, custom }) => {
        logger.debug(`Add case tool called with section_id: ${section_id}, title: ${title}`);
        try {
          const payload: CaseCreatePayload = {
            title,
            section_id,
            type_id,
            priority_id,
            refs,
            custom,
          };
          
          const result = await addCase(payload);
          logger.debug(`Add case tool completed successfully. Case ID: ${result.id}`);
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(result, null, 2),
              },
            ],
          };
        } catch (err) {
          logger.error({ err }, `Add case tool failed for section_id: ${section_id}`);
          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 = `Section ${section_id} not found. Use get_sections tool to find valid section IDs for your project.`;
          else if (e?.type === 'validation_error') message = `Validation error: ${e.message}. Check custom field values using get_case_fields tool and ensure required fields are provided.`;
          else if (e?.type === 'permission_denied') message = `Permission denied for section ${section_id}. Try a different project or section using get_projects and get_sections tools.`;
          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 for 'add_case' tool defining required/optional parameters with descriptions and validation rules.
    inputSchema: {
      title: z.string().min(1).describe('Test case title - should be descriptive and unique within the section'),
      section_id: z.number().int().positive().describe('Section ID where the case will be created. REQUIRED: Use get_sections tool first to find valid section IDs for your project/suite. Different projects have different section structures.'),
      type_id: z.number().int().positive().optional().describe('Test case type ID (e.g., 1=Acceptance, 2=Accessibility, 3=Automated, 4=Compatibility, 5=Destructive, 6=Functional, 7=Other, 8=Performance, 9=Regression, 10=Security, 11=Smoke & Sanity, 12=Usability). RECOMMENDED: Use get_cases tool to see what type_id values are used in existing cases in your target section.'),
      priority_id: z.number().int().positive().optional().describe('Priority ID (1=Low, 2=Medium, 3=High, 4=Critical). RECOMMENDED: Use get_cases tool to see what priority_id values are used in existing cases.'),
      refs: z.string().nullable().optional().describe('References (e.g., requirement IDs, JIRA tickets, user story numbers). Can be comma-separated for multiple references.'),
      custom: z.record(z.string(), z.unknown()).optional().describe('Custom fields (key-value pairs). REQUIRED: Use get_case_fields tool first to discover available custom fields and their valid values. Common fields include: custom_automation_type (1=None, 2=Playwright, 3=ChatGPT, 4=Non-Automated, 5=Partial), custom_environment (1=UAT Only, 2=UAT/Prod, 3=Demo UAT, 4=Live UAT), custom_preconds (preconditions text), custom_steps (test steps text), custom_expected (expected results text). Some custom fields are required by the project configuration.'),
    },
  • Service layer function addCase that transforms high-level payload to TestRailCaseCreateDto (adds custom_ prefix), calls TestRail client, normalizes response to CaseSummary.
    export async function addCase(payload: CaseCreatePayload): Promise<CaseSummary> {
      // Transform the payload to match TestRail API format
      const createPayload: TestRailCaseCreateDto = {
        title: payload.title,
        section_id: payload.section_id,
        type_id: payload.type_id,
        priority_id: payload.priority_id,
        refs: payload.refs,
      };
    
      // Add custom fields with proper naming convention
      if (payload.custom) {
        for (const [key, value] of Object.entries(payload.custom)) {
          // Ensure custom field keys have the 'custom_' prefix
          const fieldKey = key.startsWith('custom_') ? key : `custom_${key}`;
          createPayload[fieldKey] = value;
        }
      }
    
      const data: TestRailCaseDto = await testRailClient.addCase(payload.section_id, createPayload);
      
      // Normalize the response using the same logic as getCase
      const {
        id,
        title,
        section_id,
        type_id,
        priority_id,
        refs,
        created_on,
        updated_on,
        ...rest
      } = data;
    
      const custom: Record<string, unknown> = {};
      for (const [key, value] of Object.entries(rest)) {
        if (key.startsWith('custom_')) custom[key] = value;
      }
    
      return {
        id,
        title,
        section_id,
        type_id,
        priority_id,
        refs: refs ?? null,
        created_on,
        updated_on,
        custom: Object.keys(custom).length ? custom : undefined,
      };
    }
  • TypeScript interface CaseCreatePayload defining the structure expected by the addCase service function.
    export interface CaseCreatePayload {
      title: string;
      section_id: number;
      type_id?: number;
      priority_id?: number;
      refs?: string | null;
      custom?: Record<string, unknown>;
    }
  • HTTP client method that performs POST request to TestRail API endpoint /add_case/{sectionId} to create the case.
    async addCase(sectionId: number, caseData: TestRailCaseCreateDto): Promise<TestRailCaseDto> {
      try {
        const res = await this.http.post(`/add_case/${sectionId}`, caseData);
        if (res.status >= 200 && res.status < 300) {
          logger.info({
            message: 'Successfully created test case',
            sectionId,
            caseId: res.data.id,
            responseSize: JSON.stringify(res.data).length,
          });
          return res.data as TestRailCaseDto;
        }
        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, sectionId }, 'TestRail addCase 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