get_test_plan
Fetch a test plan with details like test case count, configurations, runs, and execution progress status using ID or title.
Instructions
Fetch a single test plan with summary details:
Included test cases count
Test plan configurations
Test plan runs
Current execution progress status
Required: id or title Optional: project_id, include_configurations, include_runs, runs_limit, runs_offset, runs_sort
Example: { "id": 812, "project_id": 16 }
or
{ "title": "Release 3.0 Regression", "project_id": 16 }
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | Test plan ID to retrieve. Accepts numeric ID or title string. | |
| title | No | Test plan title to retrieve (alternative to id). | |
| project_id | No | Project ID (uses default if not specified) | |
| include_configurations | No | Include test plan configurations in the response (default: true) | |
| include_runs | No | Include test plan runs in the response (default: true) | |
| runs_limit | No | Maximum number of runs to return (1-100, default: 20) | |
| runs_offset | No | Number of runs to skip (default: 0) | |
| runs_sort | No | Run sort expression (default: "id:desc") | id:desc |
Implementation Reference
- src/tools/test-plans/get.ts:577-993 (handler)The main handler function for the get_test_plan tool, responsible for parsing arguments, validating project context, fetching the test plan, and constructing the response.
export async function handleGetTestPlan( args: unknown ): Promise<{ content: Array<{ type: "text"; text: string }> }> { const parsed = getTestPlanSchema.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 { id, title, project_id, include_configurations, include_runs, runs_limit, runs_offset, runs_sort, } = 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 resolvedTitleInput = normalizeString(title); const idAsNumber = toNumberId(id); const idAsTitle = typeof id === "string" && toNumberId(id) === undefined ? normalizeString(id) : undefined; const lookupTitle = resolvedTitleInput ?? idAsTitle; let resolvedTestPlanId = idAsNumber; if (resolvedTestPlanId === undefined && lookupTitle) { const exactMatchesRaw = await client.listTestPlans({ projectId: resolvedProjectId, limit: 100, offset: 0, sort: "updated_at:desc", filter: { title: lookupTitle }, }); let matches = findMatchingPlansByTitle( mapPlanLookupCandidates(exactMatchesRaw), lookupTitle ); if (matches.length === 0) { const fallbackMatchesRaw = await client.listTestPlans({ projectId: resolvedProjectId, limit: 100, offset: 0, sort: "updated_at:desc", filter: { title_contains: lookupTitle }, }); matches = findMatchingPlansByTitle( mapPlanLookupCandidates(fallbackMatchesRaw), lookupTitle ); } if (matches.length === 0) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "TEST_PLAN_NOT_FOUND", message: `Test plan not found with title "${lookupTitle}" in that project.`, }, }), }, ], }; } if (matches.length > 1) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "AMBIGUOUS_TEST_PLAN_TITLE", message: `Multiple test plans matched title "${lookupTitle}". Provide ID instead.`, details: { matching_ids: matches.map((plan) => plan.id), }, }, }), }, ], }; } resolvedTestPlanId = matches[0].id; } if (resolvedTestPlanId === undefined) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "VALIDATION_ERROR", message: "Provide a numeric id or a non-empty title.", }, }), }, ], }; } const rawPlan = await client.getTestPlanRaw(resolvedTestPlanId); const plan = unwrapApiEntity(rawPlan); if (!plan) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "INVALID_TEST_PLAN", message: `Unable to parse test plan ${resolvedTestPlanId}.`, }, }), }, ], }; } const includedTestCasesCountResponse = await client.getTestPlanTestCaseCount( resolvedProjectId, resolvedTestPlanId ); const includedTestCasesCount = extractCount(includedTestCasesCountResponse); let configurations: Array<Record<string, unknown>> = []; if (include_configurations) { const rawConfigurations = await client.listTestPlanConfigurations({ projectId: resolvedProjectId, testplan: resolvedTestPlanId, limit: -1, }); configurations = rawConfigurations .map((configuration) => mapConfiguration(configuration)) .filter( (configuration): configuration is Record<string, unknown> => Boolean(configuration) ); } let runs: Array<Record<string, unknown>> = []; if (include_runs) { const rawRuns = await client.listTestPlanRegressions({ projectId: resolvedProjectId, testplan: resolvedTestPlanId, limit: runs_limit, start: runs_offset, sort: runs_sort, }); runs = rawRuns .map((run) => mapRun(run)) .filter((run): run is Record<string, unknown> => Boolean(run)); } let runCount: number | null = null; try { const runCountResponse = await client.getTestPlanRegressionCount( resolvedProjectId, { testplan: resolvedTestPlanId } ); runCount = extractCount(runCountResponse); } catch { runCount = null; } const planResults = getField<unknown>(plan, "results"); const planOverallSummary = normalizeResultSummary( planResults && typeof planResults === "object" && !Array.isArray(planResults) ? getField(planResults, "overall") ?? planResults : undefined ); const latestRunSummary = runs.length > 0 ? normalizeResultSummary(getField(runs[0], "result")) : null; let progress: ProgressPayload | null = null; if (planOverallSummary || latestRunSummary) { const summary = planOverallSummary ?? latestRunSummary!; const source: ProgressPayload["source"] = planOverallSummary ? "test_plan_results" : "latest_run_result"; const total = Object.values(summary).reduce((sum, value) => sum + value, 0); const unexecuted = getStatusCount(summary, "unexecuted"); const passed = getStatusCount(summary, "passed"); const failed = getStatusCount(summary, "failed"); const skipped = getStatusCount(summary, "skipped"); const blocked = getStatusCount(summary, "blocked"); const executed = Math.max(total - unexecuted, 0); progress = { source, status: deriveProgressStatus({ total, executed, failed, blocked, }), total, executed, unexecuted, passed, failed, skipped, blocked, executionProgressPercent: toPercent(executed, total), passRatePercent: toPercent(passed, executed), summary, }; } const status = toNumberId(getField(plan, "status")); const priority = toNumberId(getField(plan, "priority")); const testPlanFolderRaw = getField(plan, "test_plan_folder") ?? getField(plan, "testPlanFolder"); const testPlanFolderId = extractId(testPlanFolderRaw); const testPlanFolderTitle = normalizeString(getField<string>(testPlanFolderRaw, "title")) ?? normalizeString(getField<string>(testPlanFolderRaw, "name")); const releaseRaw = getField(plan, "release"); const releaseId = extractId(releaseRaw); const releaseTitle = normalizeString(getField<string>(releaseRaw, "title")) ?? normalizeString(getField<string>(releaseRaw, "name")); const createdBy = mapUser( getField(plan, "created_by") ?? getField(plan, "createdBy") ); const assignedToRaw = getArrayField(plan, "assigned_to", ["assignedTo"]); const assignedTo = (assignedToRaw ?? []) .map((user) => mapUser(user)) .filter((user): user is Record<string, unknown> => Boolean(user)); const planConfigurationCountFromPlan = getArrayField( plan, "configurations" )?.length; const configurationCount = include_configurations ? configurations.length : planConfigurationCountFromPlan ?? null; const hasMoreRuns = include_runs && (runCount !== null ? runs_offset + runs.length < runCount : runs.length === runs_limit); const normalizedPlan = { id: extractId(plan) ?? resolvedTestPlanId, ...(normalizeString(getField<string>(plan, "title")) ? { title: normalizeString(getField<string>(plan, "title")) } : {}), ...(normalizeString(getField<string>(plan, "description")) ? { description: normalizeString(getField<string>(plan, "description")) } : {}), ...(status !== undefined ? { status, statusLabel: testPlanStatusCodeToLabel[status] ?? "Unknown" } : {}), ...(priority !== undefined ? { priority, priorityLabel: testPlanPriorityCodeToLabel[priority] ?? "Unknown", } : {}), ...(typeof getField(plan, "archived") === "boolean" ? { archived: getField(plan, "archived") } : {}), ...(testPlanFolderId !== undefined ? { testPlanFolder: { id: testPlanFolderId, ...(testPlanFolderTitle ? { title: testPlanFolderTitle } : {}), }, } : {}), ...(releaseId !== undefined ? { release: { id: releaseId, ...(releaseTitle ? { title: releaseTitle } : {}), }, } : {}), ...(createdBy ? { createdBy } : {}), assignedTo, ...(normalizeString(getField<string>(plan, "start_date")) ? { startDate: normalizeString(getField<string>(plan, "start_date")) } : {}), ...(normalizeString(getField<string>(plan, "end_date")) ? { endDate: normalizeString(getField<string>(plan, "end_date")) } : {}), ...(normalizeString(getField<string>(plan, "actual_start_date")) ? { actualStartDate: normalizeString( getField<string>(plan, "actual_start_date") ), } : {}), ...(normalizeString(getField<string>(plan, "created_at")) ? { createdAt: normalizeString(getField<string>(plan, "created_at")) } : {}), ...(normalizeString(getField<string>(plan, "updated_at")) ? { updatedAt: normalizeString(getField<string>(plan, "updated_at")) } : {}), ...(normalizeString(getField<string>(plan, "last_run")) ? { lastRun: normalizeString(getField<string>(plan, "last_run")) } : {}), ...(getField(plan, "results") && typeof getField(plan, "results") === "object" && !Array.isArray(getField(plan, "results")) ? { results: getField(plan, "results") } : {}), ...(typeof getField(plan, "time_spent") === "number" ? { timeSpent: getField(plan, "time_spent") } : {}), ...(typeof getField(plan, "estimate") === "number" ? { estimate: getField(plan, "estimate") } : {}), }; return { content: [ { type: "text", text: JSON.stringify( { testPlan: normalizedPlan, summary: { included_test_cases_count: includedTestCasesCount, configuration_count: configurationCount, run_count: runCount, current_progress_status: progress?.status ?? null, execution_progress_percent: progress?.executionProgressPercent ?? null, pass_rate_percent: progress?.passRatePercent ?? null, }, progress, configurations, runs, runsPagination: include_runs ? { returned: runs.length, limit: runs_limit, offset: runs_offset, hasMore: hasMoreRuns, } : null, }, null, 2 ), }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "API_ERROR", message: getErrorMessage(error), }, }), }, ], }; } } - src/tools/test-plans/get.ts:58-64 (schema)Schema definition for validating the input arguments for the get_test_plan tool.
export const getTestPlanSchema = getTestPlanRegistrationSchema.refine( (value) => value.id !== undefined || value.title !== undefined, { message: "Either id or title is required.", path: ["id"], } ); - src/tools/test-plans/get.ts:72-142 (registration)Tool registration object containing metadata and input schema for the get_test_plan tool.
export const getTestPlanTool = { name: "get_test_plan", description: `Fetch a single test plan with summary details: - Included test cases count - Test plan configurations - Test plan runs - Current execution progress status Required: id or title Optional: project_id, include_configurations, include_runs, runs_limit, runs_offset, runs_sort Example: { "id": 812, "project_id": 16 } or { "title": "Release 3.0 Regression", "project_id": 16 }`, inputSchema: { type: "object" as const, properties: { id: { oneOf: [{ type: "number" }, { type: "string" }], description: "Test plan ID to retrieve (numeric ID or title string)", }, title: { type: "string", description: "Test plan title to retrieve (alternative to id)", }, project_id: { type: "number", description: "Project ID (optional if default is set)", }, include_configurations: { type: "boolean", default: true, description: "Include test plan configurations in the response", }, include_runs: { type: "boolean", default: true, description: "Include test plan runs in the response", }, runs_limit: { type: "number", minimum: 1, maximum: 100, default: 20, description: "Maximum number of runs to return (1-100, default: 20)", }, runs_offset: { type: "number", minimum: 0, default: 0, description: "Number of runs to skip (default: 0)", }, runs_sort: { type: "string", default: "id:desc", description: 'Run sort expression (default: "id:desc")', }, }, required: [], }, };