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
| Name | Required | Description | Default |
|---|---|---|---|
| project_id | Yes | TestRail project ID | |
| created_after | No | Only return test runs created after this date (as UNIX timestamp) | |
| created_before | No | Only return test runs created before this date (as UNIX timestamp) | |
| created_by | No | A comma-separated list of creators (user IDs) to filter by | |
| is_completed | No | 1 to return completed test runs only. 0 to return active test runs only | |
| limit | No | The number of test runs to return (max 250, default 250) | |
| milestone_id | No | A comma-separated list of milestone IDs to filter by | |
| offset | No | Where to start counting the test runs from (pagination offset) | |
| refs_filter | No | A single Reference ID (e.g. TR-a, 4291, etc.) | |
| suite_id | No | A 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), }, ], }; }, );
- src/services/testrailService.ts:718-752 (handler)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[]; }