Skip to main content
Glama
Derrbal

TestRail MCP Server

by Derrbal

get_suites

Retrieve all test suites for a specific TestRail project using the project ID to organize and access test case collections within your test management system.

Instructions

Get all test suites for a specific TestRail project by ID.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_idYesTestRail project ID

Implementation Reference

  • src/server.ts:277-317 (registration)
    Registers the MCP 'get_suites' tool with input schema (project_id) and handler that delegates to testrailService.getSuites(project_id), formats result as JSON text content, and handles errors.
    server.registerTool(
      'get_suites',
      {
        title: 'Get TestRail Suites',
        description: 'Get all test suites for a specific TestRail project by ID.',
        inputSchema: {
          project_id: z.number().int().positive().describe('TestRail project ID'),
        },
      },
      async ({ project_id }) => {
        logger.debug(`Get suites tool called with project_id: ${project_id}`);
        try {
          const result = await getSuites(project_id);
          logger.debug(`Get suites tool completed successfully for project_id: ${project_id}. Found ${result.length} suites`);
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(result, null, 2),
              },
            ],
          };
        } catch (err) {
          logger.error({ err }, `Get suites tool failed for project_id: ${project_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 = `Project ${project_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,
          };
        }
      },
  • MCP tool handler function for get_suites: calls service layer, returns JSON suites or error message.
    async ({ project_id }) => {
      logger.debug(`Get suites tool called with project_id: ${project_id}`);
      try {
        const result = await getSuites(project_id);
        logger.debug(`Get suites tool completed successfully for project_id: ${project_id}. Found ${result.length} suites`);
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      } catch (err) {
        logger.error({ err }, `Get suites tool failed for project_id: ${project_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 = `Project ${project_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,
        };
      }
  • Tool metadata and input schema validation using Zod for project_id parameter.
    {
      title: 'Get TestRail Suites',
      description: 'Get all test suites for a specific TestRail project by ID.',
      inputSchema: {
        project_id: z.number().int().positive().describe('TestRail project ID'),
      },
  • Service layer getSuites: fetches raw suites from client, normalizes custom fields into SuiteSummary[]
    export async function getSuites(projectId: number): Promise<SuiteSummary[]> {
      const suites: TestRailSuiteDto[] = await testRailClient.getSuites(projectId);
      
      return suites.map((suite) => {
        const {
          id,
          name,
          description,
          project_id,
          url,
          is_baseline,
          is_master,
          is_completed,
          completed_on,
          created_on,
          created_by,
          updated_on,
          updated_by,
          ...rest
        } = suite;
    
        const custom: Record<string, unknown> = {};
        for (const [key, value] of Object.entries(rest)) {
          if (key.startsWith('custom_')) custom[key] = value;
        }
    
        return {
          id,
          name,
          description,
          project_id,
          url,
          is_baseline,
          is_master,
          is_completed,
          completed_on,
          created_on,
          created_by,
          updated_on,
          updated_by,
          custom: Object.keys(custom).length ? custom : undefined,
        };
      });
    }
  • HTTP client implementation: GET /get_suites/{projectId}, handles direct/paginated responses, error normalization.
    async getSuites(projectId: number): Promise<TestRailSuiteDto[]> {
      try {
        const res = await this.http.get(`/get_suites/${projectId}`);
        logger.info({ 
          status: res.status, 
          dataType: typeof res.data,
          dataIsArray: Array.isArray(res.data),
          projectId 
        }, 'TestRail getSuites response info');
        
        if (res.status >= 200 && res.status < 300) {
          // Handle both direct array and paginated response formats
          let suites: TestRailSuiteDto[];
          
          if (Array.isArray(res.data)) {
            // Direct array format (most common)
            suites = res.data as TestRailSuiteDto[];
          } else if (res.data && typeof res.data === 'object' && 'suites' in res.data) {
            // Paginated format (if TestRail supports it)
            const paginatedResponse = res.data as { suites: TestRailSuiteDto[] };
            if (!Array.isArray(paginatedResponse.suites)) {
              throw Object.assign(new Error('API returned paginated response with non-array suites field'), {
                response: { status: 200 } // Make it look like a server error
              });
            }
            suites = paginatedResponse.suites;
            logger.info({ 
              returnedSuites: suites.length
            }, 'TestRail paginated suites response');
          } else {
            logger.error({ 
              status: res.status, 
              responseData: res.data,
              dataType: typeof res.data 
            }, 'TestRail getSuites returned unexpected response format');
            throw Object.assign(new Error('API returned unexpected response format'), { 
              response: { status: 200 } // Make it look like a server error
            });
          }
          
          return suites;
        }
        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, projectId }, 'TestRail getSuites 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