Skip to main content
Glama
Derrbal

TestRail MCP Server

by Derrbal

Get TestRail Sections

get_sections

Retrieve test sections for a specific project and suite with pagination controls to organize and access TestRail test management data efficiently.

Instructions

Get a list of sections for a project and test suite with optional pagination.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_idYesTestRail project ID
suite_idNoTestRail suite ID (optional if project is in single suite mode)
limitNoThe number of sections to return (max 250, default 250)
offsetNoWhere to start counting the sections from (pagination offset)

Implementation Reference

  • The main MCP tool handler function for 'get_sections'. It processes input parameters, constructs filters, calls the service layer getSections, formats the response as JSON text content, and handles errors with specific messages.
    async ({ project_id, suite_id, limit, offset }) => {
      logger.debug(`Get sections tool called with project_id: ${project_id}, suite_id: ${suite_id}`);
      try {
        const filters = {
          project_id,
          ...(suite_id !== undefined && { suite_id }),
          ...(limit !== undefined && { limit }),
          ...(offset !== undefined && { offset }),
        };
        
        const result = await getSections(filters);
        logger.debug(`Get sections tool completed successfully for project_id: ${project_id}. Found ${result.sections.length} sections (total: ${result.size})`);
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      } catch (err) {
        logger.error({ err }, `Get sections 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} or suite ${suite_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 validation for the get_sections tool parameters: project_id (required), suite_id/limit/offset (optional).
    inputSchema: {
      project_id: z.number().int().positive().describe('TestRail project ID'),
      suite_id: z.number().int().positive().optional().describe('TestRail suite ID (optional if project is in single suite mode)'),
      limit: z.number().int().positive().optional().describe('The number of sections to return (max 250, default 250)'),
      offset: z.number().int().min(0).optional().describe('Where to start counting the sections from (pagination offset)'),
    },
  • src/server.ts:515-566 (registration)
    MCP server registration of the 'get_sections' tool, including title, description, input schema, and handler function reference.
    server.registerTool(
      'get_sections',
      {
        title: 'Get TestRail Sections',
        description: 'Get a list of sections for a project and test suite with optional pagination.',
        inputSchema: {
          project_id: z.number().int().positive().describe('TestRail project ID'),
          suite_id: z.number().int().positive().optional().describe('TestRail suite ID (optional if project is in single suite mode)'),
          limit: z.number().int().positive().optional().describe('The number of sections to return (max 250, default 250)'),
          offset: z.number().int().min(0).optional().describe('Where to start counting the sections from (pagination offset)'),
        },
      },
      async ({ project_id, suite_id, limit, offset }) => {
        logger.debug(`Get sections tool called with project_id: ${project_id}, suite_id: ${suite_id}`);
        try {
          const filters = {
            project_id,
            ...(suite_id !== undefined && { suite_id }),
            ...(limit !== undefined && { limit }),
            ...(offset !== undefined && { offset }),
          };
          
          const result = await getSections(filters);
          logger.debug(`Get sections tool completed successfully for project_id: ${project_id}. Found ${result.sections.length} sections (total: ${result.size})`);
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(result, null, 2),
              },
            ],
          };
        } catch (err) {
          logger.error({ err }, `Get sections 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} or suite ${suite_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 adapter for get_sections: maps input filters to client params, calls TestRail client, normalizes response by extracting custom_ fields into a custom object for each section.
    export async function getSections(filters: GetSectionsFilters): Promise<SectionsResponse> {
      // Transform service filters to client parameters
      const clientParams: GetSectionsParams = {
        project_id: filters.project_id,
        suite_id: filters.suite_id,
        limit: filters.limit,
        offset: filters.offset,
      };
    
      const response: TestRailSectionsResponse = await testRailClient.getSections(clientParams);
      
      // Transform each section using the same pattern as other entities
      const transformedSections: SectionSummary[] = response.sections.map((sectionData) => {
        const {
          depth,
          display_order,
          id,
          name,
          parent_id,
          suite_id,
          ...rest
        } = sectionData;
    
        const custom: Record<string, unknown> = {};
        for (const [key, value] of Object.entries(rest)) {
          if (key.startsWith('custom_')) custom[key] = value;
        }
    
        return {
          depth,
          display_order,
          id,
          name,
          parent_id,
          suite_id,
          custom: Object.keys(custom).length ? custom : undefined,
        };
      });
    
      return {
        offset: response.offset,
        limit: response.limit,
        size: response.size,
        _links: response._links,
        sections: transformedSections,
      };
    }
  • Low-level TestRailClient getSections method: constructs API URL with query params for /get_sections/{project_id}, performs HTTP GET, validates paginated response format, handles retries and errors.
    async getSections(params: GetSectionsParams): Promise<TestRailSectionsResponse> {
      try {
        // Build query parameters
        const queryParams = new URLSearchParams();
        
        // Handle suite_id parameter
        if (params.suite_id !== undefined) {
          queryParams.append('suite_id', params.suite_id.toString());
        }
        
        // Handle pagination parameters
        if (params.limit !== undefined) {
          queryParams.append('limit', params.limit.toString());
        }
        if (params.offset !== undefined) {
          queryParams.append('offset', params.offset.toString());
        }
        
        const queryString = queryParams.toString();
        const url = `/get_sections/${params.project_id}${queryString ? `&${queryString}` : ''}`;
        
        const res = await this.http.get(url);
        logger.info({ 
          status: res.status, 
          dataType: typeof res.data,
          hasSectionsProperty: res.data && typeof res.data === 'object' && 'sections' in res.data,
          projectId: params.project_id,
          suiteId: params.suite_id,
          queryParams: Object.fromEntries(queryParams)
        }, 'TestRail getSections response info');
        
        if (res.status >= 200 && res.status < 300) {
          // TestRail get_sections always returns paginated format
          if (res.data && typeof res.data === 'object' && 'sections' in res.data) {
            const paginatedResponse = res.data as TestRailSectionsResponse;
            if (!Array.isArray(paginatedResponse.sections)) {
              throw Object.assign(new Error('API returned response with non-array sections field'), {
                response: { status: 200 } // Make it look like a server error
              });
            }
            logger.info({ 
              totalSections: paginatedResponse.size,
              returnedSections: paginatedResponse.sections.length,
              offset: paginatedResponse.offset,
              limit: paginatedResponse.limit,
              hasNext: paginatedResponse._links?.next !== null,
              hasPrev: paginatedResponse._links?.prev !== null
            }, 'TestRail getSections paginated response');
            
            return paginatedResponse;
          } else {
            logger.error({ 
              status: res.status, 
              responseData: res.data,
              dataType: typeof res.data 
            }, 'TestRail getSections returned unexpected response format');
            throw Object.assign(new Error('API returned unexpected response format'), { 
              response: { status: 200 } // Make it look like a server error
            });
          }
        }
        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, params }, 'TestRail getSections failed');
        throw normalized;
      }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'optional pagination' which hints at list behavior, but doesn't describe return format, error conditions, rate limits, authentication needs, or what happens with invalid inputs. For a read operation with 4 parameters, this lacks critical operational context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured sentence that efficiently conveys the core action, scope, and key feature. It's front-loaded with the main purpose and avoids redundancy or unnecessary details. Every word earns its place, making it highly concise and readable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 4 parameters, no annotations, and no output schema, the description is incomplete. It lacks details on return values, error handling, and practical usage scenarios. For a tool that fetches structured data, more context is needed to help an agent use it effectively, especially without annotations to fill gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all 4 parameters. The description adds minimal value beyond the schema, mentioning 'optional pagination' which loosely relates to 'limit' and 'offset', but doesn't clarify semantics like the relationship between project_id and suite_id or default behaviors. Baseline 3 is appropriate as the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Get a list of') and resource ('sections'), specifying the scope ('for a project and test suite') and optional feature ('with optional pagination'). It distinguishes from siblings like 'get_cases' or 'get_suites' by focusing on sections, but doesn't explicitly contrast with them. The purpose is specific and actionable, though not maximally differentiated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It mentions the context ('for a project and test suite') but doesn't specify use cases, prerequisites, or exclusions. With siblings like 'get_cases' or 'get_suites' available, there's no indication of when sections are needed over those resources, leaving usage ambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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