jira_list_sprints
Retrieve and filter Jira sprints by board, project, or state using host URL, credentials, and optional parameters to manage sprint data efficiently.
Instructions
Lists current sprints in Jira with filtering options
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| apiToken | No | API token for Jira authentication | |
| boardId | No | Optional Jira board ID to filter sprints by a specific board | |
| No | Email address associated with the Jira account | ||
| jiraHost | No | The Jira host URL (e.g., 'your-domain.atlassian.net') | |
| projectKey | No | Optional project key to find sprints associated with the project | |
| state | No | Sprint state to filter by (active, future, closed, or all) | active |
Implementation Reference
- src/tools/listSprints.ts:66-285 (handler)Core handler function that executes the jira_list_sprints tool: validates args, authenticates, fetches boards/sprints/issues from Jira API, generates formatted Markdown response with tables.export async function listSprints(args: any) { const validatedArgs = await JiraSprintRequestSchema.validate(args); const jiraHost = validatedArgs.jiraHost || process.env.JIRA_HOST; const email = validatedArgs.email || process.env.JIRA_EMAIL; const apiToken = validatedArgs.apiToken || process.env.JIRA_API_TOKEN; const boardId = validatedArgs.boardId; const projectKey = validatedArgs.projectKey; const state = validatedArgs.state || 'active'; if (!jiraHost || !email || !apiToken) { throw new Error('Missing required authentication credentials. Please provide jiraHost, email, and apiToken.'); } validateCredentials(jiraHost, email, apiToken); const authHeader = createAuthHeader(email, apiToken); try { let boardIds: string[] = []; if (boardId) { boardIds.push(boardId); } else if (projectKey) { const boardResponse = await axios.get(`https://${jiraHost}/rest/agile/1.0/board`, { params: { projectKeyOrId: projectKey }, headers: { 'Authorization': authHeader, 'Accept': 'application/json', }, }); if (boardResponse.data.values && boardResponse.data.values.length > 0) { boardIds = boardResponse.data.values.map((board: any) => board.id); } else { return { content: [{ type: "text", text: `# No Boards Found\n\nNo boards were found for project key: ${projectKey}` }], isError: false, }; } } else { const boardResponse = await axios.get(`https://${jiraHost}/rest/agile/1.0/board`, { headers: { 'Authorization': authHeader, 'Accept': 'application/json', }, }); if (boardResponse.data.values && boardResponse.data.values.length > 0) { boardIds = boardResponse.data.values.slice(0, 5).map((board: any) => board.id); } else { return { content: [{ type: "text", text: `# No Boards Found\n\nNo boards were found in your Jira instance.` }], isError: false, }; } } let allSprints: any[] = []; for (const bId of boardIds) { try { const sprintParams: any = {}; if (state !== 'all') { sprintParams.state = state; } const sprintResponse = await axios.get(`https://${jiraHost}/rest/agile/1.0/board/${bId}/sprint`, { params: sprintParams, headers: { 'Authorization': authHeader, 'Accept': 'application/json', }, }); if (sprintResponse.data.values && sprintResponse.data.values.length > 0) { const sprintsWithBoardInfo = sprintResponse.data.values.map((sprint: any) => ({ ...sprint, boardId: bId })); allSprints = [...allSprints, ...sprintsWithBoardInfo]; } } catch (error) { console.warn(`Could not fetch sprints for board ${bId}`); } } if (allSprints.length === 0) { let message = `# No Sprints Found\n\n`; if (state !== 'all') { message += `No ${state} sprints were found`; } else { message += `No sprints were found`; } if (boardId) { message += ` for board ID: ${boardId}`; } else if (projectKey) { message += ` for project key: ${projectKey}`; } return { content: [{ type: "text", text: message }], isError: false, }; } allSprints.sort((a, b) => { const dateA = a.startDate ? new Date(a.startDate).getTime() : 0; const dateB = b.startDate ? new Date(b.startDate).getTime() : 0; return dateB - dateA; }); let formattedResponse = `# ${state.charAt(0).toUpperCase() + state.slice(1)} Sprints\n\n`; formattedResponse += `| ID | Name | Board | Status | Start Date | End Date |\n`; formattedResponse += `|----|------|-------|--------|------------|----------|\n`; allSprints.forEach(sprint => { const startDate = sprint.startDate ? new Date(sprint.startDate).toLocaleDateString() : 'Not started'; const endDate = sprint.endDate ? new Date(sprint.endDate).toLocaleDateString() : 'Not set'; const status = sprint.state.charAt(0).toUpperCase() + sprint.state.slice(1); formattedResponse += `| ${sprint.id} | ${sprint.name} | ${sprint.boardId} | ${status} | ${startDate} | ${endDate} |\n`; }); formattedResponse += `\n## Sprint Details\n\n`; for (const sprint of allSprints) { formattedResponse += `### ${sprint.name} (ID: ${sprint.id})\n\n`; formattedResponse += `**Board ID:** ${sprint.boardId}\n`; formattedResponse += `**Status:** ${sprint.state.charAt(0).toUpperCase() + sprint.state.slice(1)}\n`; if (sprint.startDate) { formattedResponse += `**Start Date:** ${new Date(sprint.startDate).toLocaleString()}\n`; } if (sprint.endDate) { formattedResponse += `**End Date:** ${new Date(sprint.endDate).toLocaleString()}\n`; } if (sprint.goal) { formattedResponse += `**Goal:** ${sprint.goal}\n`; } formattedResponse += `**View in Jira:** https://${jiraHost}/jira/software/projects/${projectKey || 'browse'}/boards/${sprint.boardId}/sprints/${sprint.id}\n\n`; try { const issuesResponse = await axios.get(`https://${jiraHost}/rest/agile/1.0/sprint/${sprint.id}/issue`, { params: { fields: 'summary,status,assignee,issuetype' }, headers: { 'Authorization': authHeader, 'Accept': 'application/json', }, }); if (issuesResponse.data.issues && issuesResponse.data.issues.length > 0) { const issues = issuesResponse.data.issues; formattedResponse += `#### Issues (${issues.length})\n\n`; formattedResponse += `| Key | Summary | Type | Status | Assignee |\n`; formattedResponse += `|-----|---------|------|--------|----------|\n`; issues.forEach((issue: any) => { const assignee = issue.fields.assignee ? issue.fields.assignee.displayName : 'Unassigned'; const status = issue.fields.status ? issue.fields.status.name : 'Unknown'; const type = issue.fields.issuetype ? issue.fields.issuetype.name : 'Task'; formattedResponse += `| [${issue.key}](https://${jiraHost}/browse/${issue.key}) | ${issue.fields.summary} | ${type} | ${status} | ${assignee} |\n`; }); } else { formattedResponse += `No issues found in this sprint.\n`; } } catch (error) { formattedResponse += `Could not fetch issues for this sprint.\n`; } formattedResponse += `\n---\n\n`; } return { content: [{ type: "text", text: formattedResponse }], isError: false, }; } catch (error: any) { let errorMsg = "An error occurred while retrieving sprints."; if (error.response) { errorMsg = `Error ${error.response.status}: ${JSON.stringify(error.response.data) || error.message}`; } else if (error.message) { errorMsg = error.message; } return { content: [{ type: "text", text: `# Error\n\n${errorMsg}` }], isError: true, }; } }
- src/validators/index.ts:146-163 (schema)Yup schema for validating input parameters to the jira_list_sprints handler, extending base JiraApiRequestSchema with boardId, projectKey, state.export const JiraSprintRequestSchema = JiraApiRequestSchema.shape({ boardId: yup.string() .optional() .test( 'valid-board-id', 'Board ID must be numeric', (value) => !value || /^\d+$/.test(value) ), projectKey: yup.string() .optional() .matches(/^[A-Z][A-Z0-9_]+$/, "Invalid project key format. Only uppercase letters, numbers, and underscores are allowed"), state: yup.string() .oneOf( ['active', 'future', 'closed', 'all'], "Sprint state must be one of: active, future, closed, all" ) .default('active'), });
- src/handlers/handlerTools.ts:52-55 (registration)Tool registration mapping 'jira_list_sprints' to its validation schema and handler function in the toolConfigs object used by handleCallTool.jira_list_sprints: { schema: JiraSprintRequestSchema, handler: listSprints }
- src/tools/listSprints.ts:12-50 (schema)JSON schema definition in tool description object used for MCP tool listing.export const listSprintsToolDescription = { name: "jira_list_sprints", description: "Lists current sprints in Jira with filtering options", inputSchema: { type: "object", properties: { jiraHost: { type: "string", description: "The Jira host URL (e.g., 'your-domain.atlassian.net')", default: process.env.JIRA_HOST || "", }, email: { type: "string", description: "Email address associated with the Jira account", default: process.env.JIRA_EMAIL || "", }, apiToken: { type: "string", description: "API token for Jira authentication", default: process.env.JIRA_API_TOKEN || "", }, boardId: { type: "string", description: "Optional Jira board ID to filter sprints by a specific board", }, projectKey: { type: "string", description: "Optional project key to find sprints associated with the project", }, state: { type: "string", description: "Sprint state to filter by (active, future, closed, or all)", default: "active", enum: ["active", "future", "closed", "all"] }, }, required: [], }, };
- src/handlers/handlerListTools.ts:155-179 (registration)Tool description and schema provided in handleListTools response for MCP tool discovery.{ name: "jira_list_sprints", description: "Lists current sprints in Jira with filtering options", inputSchema: { type: "object", properties: { ...getCommonJiraProperties(), boardId: { type: "string", description: "Optional Jira board ID to filter sprints by a specific board", }, projectKey: { type: "string", description: "Optional project key to find sprints associated with the project", }, state: { type: "string", description: "Sprint state to filter by (active, future, closed, or all)", default: "active", enum: ["active", "future", "closed", "all"] }, }, required: [], }, },