Skip to main content
Glama

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
NameRequiredDescriptionDefault
apiTokenNoAPI token for Jira authentication
boardIdNoOptional Jira board ID to filter sprints by a specific board
emailNoEmail address associated with the Jira account
jiraHostNoThe Jira host URL (e.g., 'your-domain.atlassian.net')
projectKeyNoOptional project key to find sprints associated with the project
stateNoSprint state to filter by (active, future, closed, or all)active

Implementation Reference

  • 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, }; } }
  • 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'), });
  • 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 }
  • 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: [], }, };
  • 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: [], }, },

Other Tools

Related Tools

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/samuelrizzo/jira-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server