Skip to main content
Glama
Derrbal

TestRail MCP Server

by Derrbal

get_runs

Retrieve test runs for a specific project with filtering options like date ranges, creators, completion status, and pagination controls. Excludes test runs that are part of test plans.

Instructions

Get a list of test runs for a project with optional filtering and pagination. Only returns test runs that are not part of a test plan.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_idYesTestRail project ID
created_afterNoOnly return test runs created after this date (as UNIX timestamp)
created_beforeNoOnly return test runs created before this date (as UNIX timestamp)
created_byNoA comma-separated list of creators (user IDs) to filter by
is_completedNo1 to return completed test runs only. 0 to return active test runs only
limitNoThe number of test runs to return (max 250, default 250)
milestone_idNoA comma-separated list of milestone IDs to filter by
offsetNoWhere to start counting the test runs from (pagination offset)
refs_filterNoA single Reference ID (e.g. TR-a, 4291, etc.)
suite_idNoA comma-separated list of test suite IDs to filter by

Implementation Reference

  • src/server.ts:568-615 (registration)
    Registers the MCP tool 'get_runs' with Zod input schema defining parameters like project_id, filters, pagination. The thin handler constructs filters and delegates to service getRuns, formats JSON response.
    logger.debug('Registering get_runs tool...');
    
    server.registerTool(
      'get_runs',
      {
        title: 'Get TestRail Runs',
        description: 'Get a list of test runs for a project with optional filtering and pagination. Only returns test runs that are not part of a test plan.',
        inputSchema: {
          project_id: z.number().int().positive().describe('TestRail project ID'),
          created_after: z.number().int().optional().describe('Only return test runs created after this date (as UNIX timestamp)'),
          created_before: z.number().int().optional().describe('Only return test runs created before this date (as UNIX timestamp)'),
          created_by: z.array(z.number().int().positive()).optional().describe('A comma-separated list of creators (user IDs) to filter by'),
          is_completed: z.boolean().optional().describe('1 to return completed test runs only. 0 to return active test runs only'),
          limit: z.number().int().positive().optional().describe('The number of test runs to return (max 250, default 250)'),
          milestone_id: z.array(z.number().int().positive()).optional().describe('A comma-separated list of milestone IDs to filter by'),
          offset: z.number().int().min(0).optional().describe('Where to start counting the test runs from (pagination offset)'),
          refs_filter: z.string().optional().describe('A single Reference ID (e.g. TR-a, 4291, etc.)'),
          suite_id: z.array(z.number().int().positive()).optional().describe('A comma-separated list of test suite IDs to filter by'),
        },
      },
      async ({ project_id, created_after, created_before, created_by, is_completed, limit, milestone_id, offset, refs_filter, suite_id }) => {
        logger.debug(`Get runs tool called with project_id: ${project_id}`);
        
        const filters = {
          project_id,
          created_after,
          created_before,
          created_by,
          is_completed,
          limit,
          milestone_id,
          offset,
          refs_filter,
          suite_id,
        };
    
        const result = await getRuns(filters);
        logger.debug(`Get runs tool completed. Found ${result.runs.length} runs.`);
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      },
    );
  • Core handler logic: maps GetRunsFilters to GetRunsParams, calls testRailClient.getRuns(), transforms TestRailRunsResponse to RunsResponse by extracting custom fields into RunSummary objects.
    export async function getRuns(filters: GetRunsFilters): Promise<RunsResponse> {
      // Transform service filters to client parameters
      const clientParams: GetRunsParams = {
        project_id: filters.project_id,
        created_after: filters.created_after,
        created_before: filters.created_before,
        created_by: filters.created_by,
        is_completed: filters.is_completed,
        limit: filters.limit,
        milestone_id: filters.milestone_id,
        offset: filters.offset,
        refs_filter: filters.refs_filter,
        suite_id: filters.suite_id,
      };
    
      const response: TestRailRunsResponse = await testRailClient.getRuns(clientParams);
    
      // Transform the response to normalize custom fields
      const transformedRuns: RunSummary[] = response.runs.map((run: TestRailRunDto) => {
        const { id, name, ...customFields } = run;
        return {
          id,
          name,
          custom: Object.keys(customFields).length > 0 ? customFields : undefined,
        };
      });
    
      return {
        offset: response.offset,
        limit: response.limit,
        size: response.size,
        _links: response._links,
        runs: transformedRuns,
      };
    }
  • TypeScript interface defining the filter parameters accepted by the getRuns service handler function.
    export interface GetRunsFilters {
      project_id: number;
      created_after?: number;
      created_before?: number;
      created_by?: number[];
      is_completed?: boolean;
      limit?: number;
      milestone_id?: number[];
      offset?: number;
      refs_filter?: string;
      suite_id?: number[];
    }
  • Low-level HTTP client implementation: constructs GET /get_runs/{project_id}?... URL with query params from GetRunsParams, calls axios, returns raw TestRail API response, with retry and error normalization.
    async getRuns(params: GetRunsParams): Promise<TestRailRunsResponse> {
      try {
        // Build query parameters
        const queryParams = new URLSearchParams();
        
        // Handle date filters
        if (params.created_after !== undefined) {
          queryParams.append('created_after', params.created_after.toString());
        }
        if (params.created_before !== undefined) {
          queryParams.append('created_before', params.created_before.toString());
        }
        
        // Handle created_by filter (comma-separated list)
        if (params.created_by && params.created_by.length > 0) {
          queryParams.append('created_by', params.created_by.join(','));
        }
        
        // Handle completion status
        if (params.is_completed !== undefined) {
          queryParams.append('is_completed', params.is_completed ? '1' : '0');
        }
        
        // Handle pagination parameters
        if (params.limit !== undefined) {
          queryParams.append('limit', params.limit.toString());
        }
        if (params.offset !== undefined) {
          queryParams.append('offset', params.offset.toString());
        }
        
        // Handle milestone filter (comma-separated list)
        if (params.milestone_id && params.milestone_id.length > 0) {
          queryParams.append('milestone_id', params.milestone_id.join(','));
        }
        
        // Handle refs filter
        if (params.refs_filter) {
          queryParams.append('refs_filter', params.refs_filter);
        }
        
        // Handle suite filter (comma-separated list)
        if (params.suite_id && params.suite_id.length > 0) {
          queryParams.append('suite_id', params.suite_id.join(','));
        }
        
        const queryString = queryParams.toString();
        const url = `/get_runs/${params.project_id}${queryString ? `&${queryString}` : ''}`;
        
        const res = await this.http.get(url);
        logger.info({
          message: 'Successfully retrieved test runs',
          projectId: params.project_id,
          filters: params,
          responseSize: res.data.runs?.length || 0,
        });
        
        return res.data;
      } catch (error) {
        const normalized = this.normalizeError(error);
        const safeDetails = this.getSafeErrorDetails(error);
        logger.error({
          message: 'Failed to retrieve test runs',
          projectId: params.project_id,
          filters: params,
          error: normalized,
          details: safeDetails,
        });
        throw normalized;
      }
    }
  • TypeScript interface for parameters passed from service to client getRuns method, matching TestRail API query parameters.
    export interface GetRunsParams {
      project_id: number;
      created_after?: number;
      created_before?: number;
      created_by?: number[];
      is_completed?: boolean;
      limit?: number;
      milestone_id?: number[];
      offset?: number;
      refs_filter?: string;
      suite_id?: number[];
    }

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