list_test_plans
Retrieve test plans from a TestCollab project with filtering, sorting, and pagination options to organize testing workflows.
Instructions
List test plans from a TestCollab project with optional filtering, sorting, and pagination.
Optional filters:
title_contains
status: 0/1/2/3 or draft/ready/finished/finished_with_failures
priority: 0/1/2 or low/normal/high
archived: true/false
created_by: creator user ID
test_plan_folder: folder ID or folder title
release: release ID or release title
created_at_from/to, updated_at_from/to, start_date_from/to, end_date_from/to, last_run_from/to
filter: raw filter object for advanced keys (merged with explicit filters)
Example: { "project_id": 16, "title_contains": "Release", "status": "ready", "priority": "high", "created_by": 27, "sort_by": "updated_at", "sort_order": "desc", "limit": 25, "offset": 0 }
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_id | No | Project ID (uses TC_DEFAULT_PROJECT env var if not specified) | |
| limit | No | Maximum results to return (1-100, default: 25) | |
| offset | No | Number of results to skip (default: 0) | |
| sort_by | No | Sort field (default: updated_at) | updated_at |
| sort_order | No | Sort order (default: desc) | desc |
| title_contains | No | Filter plans whose title contains this string | |
| status | No | Filter by status: 0/"draft", 1/"ready", 2/"finished", 3/"finished_with_failures" | |
| priority | No | Filter by priority: 0/"low", 1/"normal", 2/"high" | |
| archived | No | Filter by archived state | |
| created_by | No | Filter by creator user ID | |
| test_plan_folder | No | Filter by test plan folder ID or folder title | |
| release | No | Filter by release ID or release title | |
| created_at_from | No | Filter by created_at >= this ISO date/time | |
| created_at_to | No | Filter by created_at <= this ISO date/time | |
| updated_at_from | No | Filter by updated_at >= this ISO date/time | |
| updated_at_to | No | Filter by updated_at <= this ISO date/time | |
| start_date_from | No | Filter by start_date >= this date (YYYY-MM-DD) | |
| start_date_to | No | Filter by start_date <= this date (YYYY-MM-DD) | |
| end_date_from | No | Filter by end_date >= this date (YYYY-MM-DD) | |
| end_date_to | No | Filter by end_date <= this date (YYYY-MM-DD) | |
| last_run_from | No | Filter by last_run >= this ISO date/time | |
| last_run_to | No | Filter by last_run <= this ISO date/time | |
| filter | No | Advanced raw filter object (Strapi-style query keys, e.g. title_contains, created_at_gte, created_by, test_plan_folder, release) |
Implementation Reference
- src/tools/test-plans/list.ts:476-853 (handler)The main handler function for the "list_test_plans" tool, which processes arguments, applies filters, calls the API client, and formats the result.
export async function handleListTestPlans( args: unknown ): Promise<{ content: Array<{ type: "text"; text: string }> }> { const parsed = listTestPlansSchema.safeParse(args); if (!parsed.success) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "VALIDATION_ERROR", message: "Invalid input parameters", details: parsed.error.errors, }, }), }, ], }; } const { project_id, limit, offset, sort_by, sort_order, title_contains, status, priority, archived, created_by, test_plan_folder, release, created_at_from, created_at_to, updated_at_from, updated_at_to, start_date_from, start_date_to, end_date_from, end_date_to, last_run_from, last_run_to, filter, } = parsed.data; const requestContext = getRequestContext(); const envConfig = requestContext ? null : getConfig(); const resolvedProjectId = project_id ?? requestContext?.defaultProjectId ?? envConfig?.defaultProjectId; if (!resolvedProjectId) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "MISSING_PROJECT_ID", message: "project_id is required. Either provide it in the request or set TC_DEFAULT_PROJECT environment variable.", }, }), }, ], }; } try { const client = getApiClient(); const mergedFilter: Record<string, unknown> = filter ? { ...filter } : {}; if (test_plan_folder !== undefined) { const numericFolderId = toNumberId(test_plan_folder); if (numericFolderId !== undefined) { mergedFilter.test_plan_folder = numericFolderId; } else { const folderTitle = normalizeString(test_plan_folder); if (!folderTitle) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "INVALID_TEST_PLAN_FOLDER", message: "test_plan_folder must be a numeric ID or non-empty folder title.", }, }), }, ], }; } const cachedContext = getCachedProjectContext(resolvedProjectId); const cachedFolders = mapTestPlanFoldersForLookup( Array.isArray(cachedContext?.test_plan_folders) ? cachedContext.test_plan_folders : [] ); let matchedFolders = findFoldersByTitle(cachedFolders, folderTitle); if (matchedFolders.length !== 1) { const folders = await client.listTestPlanFolders(resolvedProjectId); const liveFolders = mapTestPlanFoldersForLookup( Array.isArray(folders) ? folders : [] ); matchedFolders = findFoldersByTitle(liveFolders, folderTitle); } if (matchedFolders.length === 0) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "TEST_PLAN_FOLDER_NOT_FOUND", message: `Test plan folder not found with title "${folderTitle}" in that project.`, }, }), }, ], }; } if (matchedFolders.length > 1) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "AMBIGUOUS_TEST_PLAN_FOLDER", message: `Multiple folders matched "${folderTitle}". Provide folder ID instead.`, details: { matching_ids: matchedFolders.map((folder) => folder.id), }, }, }), }, ], }; } mergedFilter.test_plan_folder = matchedFolders[0].id; } } if (release !== undefined) { const numericReleaseId = toNumberId(release); if (numericReleaseId !== undefined) { mergedFilter.release = numericReleaseId; } else { const releaseTitle = normalizeString(release); if (!releaseTitle) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "INVALID_RELEASE", message: "release must be a numeric ID or non-empty release title.", }, }), }, ], }; } const cachedContext = getCachedProjectContext(resolvedProjectId); const cachedReleases = mapReleasesForLookup( Array.isArray(cachedContext?.releases) ? cachedContext.releases : [] ); let matchedReleases = findReleasesByTitle(cachedReleases, releaseTitle); if (matchedReleases.length !== 1) { const releases = await client.listReleases(resolvedProjectId); const liveReleases = mapReleasesForLookup( Array.isArray(releases) ? releases : [] ); matchedReleases = findReleasesByTitle(liveReleases, releaseTitle); } if (matchedReleases.length === 0) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "RELEASE_NOT_FOUND", message: `Release not found with title "${releaseTitle}" in that project.`, }, }), }, ], }; } if (matchedReleases.length > 1) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "AMBIGUOUS_RELEASE", message: `Multiple releases matched "${releaseTitle}". Provide release ID instead.`, details: { matching_ids: matchedReleases.map((item) => item.id), }, }, }), }, ], }; } mergedFilter.release = matchedReleases[0].id; } } if (title_contains !== undefined) { mergedFilter.title_contains = title_contains; } const statusCode = toStatusCode(status); if (statusCode !== undefined) { mergedFilter.status = statusCode; } const priorityCode = toPriorityCode(priority); if (priorityCode !== undefined) { mergedFilter.priority = priorityCode; } if (archived !== undefined) { mergedFilter.archived = archived; } if (created_by !== undefined) { mergedFilter.created_by = created_by; } if (created_at_from !== undefined) { mergedFilter.created_at_gte = created_at_from; } if (created_at_to !== undefined) { mergedFilter.created_at_lte = created_at_to; } if (updated_at_from !== undefined) { mergedFilter.updated_at_gte = updated_at_from; } if (updated_at_to !== undefined) { mergedFilter.updated_at_lte = updated_at_to; } if (start_date_from !== undefined) { mergedFilter.start_date_gte = start_date_from; } if (start_date_to !== undefined) { mergedFilter.start_date_lte = start_date_to; } if (end_date_from !== undefined) { mergedFilter.end_date_gte = end_date_from; } if (end_date_to !== undefined) { mergedFilter.end_date_lte = end_date_to; } if (last_run_from !== undefined) { mergedFilter.last_run_gte = last_run_from; } if (last_run_to !== undefined) { mergedFilter.last_run_lte = last_run_to; } const rows = await client.listTestPlans({ projectId: resolvedProjectId, limit, offset, sort: `${sort_by}:${sort_order}`, ...(Object.keys(mergedFilter).length > 0 ? { filter: mergedFilter } : {}), }); const testPlans = rows.map((plan) => { const planStatus = typeof plan.status === "number" ? statusCodeToLabel[plan.status] : undefined; const planPriority = typeof plan.priority === "number" ? priorityCodeToLabel[plan.priority] : undefined; return { id: plan.id, title: plan.title, description: plan.description, status: plan.status, statusLabel: planStatus ?? "Unknown", priority: plan.priority, priorityLabel: planPriority ?? "Unknown", archived: plan.archived, testPlanFolder: plan.testPlanFolder ? { id: plan.testPlanFolder.id, title: plan.testPlanFolder.title, } : null, release: plan.release ? { id: plan.release.id, title: plan.release.name, } : null, createdBy: plan.createdBy ? { id: plan.createdBy.id, name: plan.createdBy.name, ...(plan.createdBy.username ? { username: plan.createdBy.username } : {}), } : null, assignedTo: (plan.assignedTo ?? []).map((user) => ({ id: user.id, name: user.name, ...(user.username ? { username: user.username } : {}), })), startDate: plan.startDate, endDate: plan.endDate, actualStartDate: plan.actualStartDate, lastRun: plan.lastRun, createdAt: plan.createdAt, updatedAt: plan.updatedAt, }; }); return { content: [ { type: "text", text: JSON.stringify( { testPlans, returned: testPlans.length, limit, offset, hasMore: testPlans.length === limit, }, null, 2 ), }, ], }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; return { content: [ { type: "text", text: JSON.stringify({ error: { code: "API_ERROR", message, }, }), }, ], }; } } - src/tools/test-plans/list.ts:38-136 (schema)The Zod schema definition for input validation of the "list_test_plans" tool.
export const listTestPlansSchema = z.object({ project_id: z .number() .optional() .describe("Project ID (uses TC_DEFAULT_PROJECT env var if not specified)"), limit: z .number() .min(1) .max(100) .default(25) .describe("Maximum results to return (1-100, default: 25)"), offset: z .number() .min(0) .default(0) .describe("Number of results to skip (default: 0)"), sort_by: sortBySchema .default("updated_at") .describe("Sort field (default: updated_at)"), sort_order: z .enum(["asc", "desc"]) .default("desc") .describe("Sort order (default: desc)"), title_contains: z .string() .min(1) .optional() .describe("Filter plans whose title contains this string"), status: statusInputSchema .optional() .describe( 'Filter by status: 0/"draft", 1/"ready", 2/"finished", 3/"finished_with_failures"' ), priority: priorityInputSchema .optional() .describe('Filter by priority: 0/"low", 1/"normal", 2/"high"'), archived: z .boolean() .optional() .describe("Filter by archived state"), created_by: z .number() .optional() .describe("Filter by creator user ID"), test_plan_folder: z .union([z.number(), z.string()]) .optional() .describe("Filter by test plan folder ID or folder title"), release: z .union([z.number(), z.string()]) .optional() .describe("Filter by release ID or release title"), created_at_from: z .string() .optional() .describe("Filter by created_at >= this ISO date/time"), created_at_to: z .string() .optional() .describe("Filter by created_at <= this ISO date/time"), updated_at_from: z .string() .optional() .describe("Filter by updated_at >= this ISO date/time"), updated_at_to: z .string() .optional() .describe("Filter by updated_at <= this ISO date/time"), start_date_from: z .string() .optional() .describe("Filter by start_date >= this date (YYYY-MM-DD)"), start_date_to: z .string() .optional() .describe("Filter by start_date <= this date (YYYY-MM-DD)"), end_date_from: z .string() .optional() .describe("Filter by end_date >= this date (YYYY-MM-DD)"), end_date_to: z .string() .optional() .describe("Filter by end_date <= this date (YYYY-MM-DD)"), last_run_from: z .string() .optional() .describe("Filter by last_run >= this ISO date/time"), last_run_to: z .string() .optional() .describe("Filter by last_run <= this ISO date/time"), filter: z .record(z.unknown()) .optional() .describe( "Advanced raw filter object (Strapi-style query keys, e.g. title_contains, created_at_gte, created_by, test_plan_folder, release)" ), }); - src/tools/test-plans/list.ts:144-145 (registration)Tool registration object including the tool name and schema definition.
export const listTestPlansTool = { name: "list_test_plans",