Skip to main content
Glama
index.ts•13.9 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from "@modelcontextprotocol/sdk/types.js"; import * as dotenv from "dotenv"; import { JiraClient } from "./jira-client.js"; dotenv.config(); // Validate required environment variables const requiredEnvVars = ["JIRA_HOST", "JIRA_EMAIL", "JIRA_API_TOKEN"]; for (const envVar of requiredEnvVars) { if (!process.env[envVar]) { throw new Error(`Missing required environment variable: ${envVar}`); } } // Initialize Jira client const jiraClient = new JiraClient({ host: process.env.JIRA_HOST!, email: process.env.JIRA_EMAIL!, apiToken: process.env.JIRA_API_TOKEN!, }); // Define available tools const TOOLS: Tool[] = [ { name: "jira_search_issues", description: "Search for Jira issues using JQL (Jira Query Language). Returns a list of issues matching the query.", inputSchema: { type: "object", properties: { jql: { type: "string", description: 'JQL query string (e.g., "project = PROJ AND status = Open")', }, maxResults: { type: "number", description: "Maximum number of results to return (default: 50)", default: 50, }, startAt: { type: "number", description: "Index of the first result to return (default: 0)", default: 0, }, }, required: ["jql"], }, }, { name: "jira_get_issue", description: "Get detailed information about a specific Jira issue by its key.", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: 'The issue key (e.g., "PROJ-123")', }, }, required: ["issueKey"], }, }, { name: "jira_create_issue", description: "Create a new Jira issue in a specified project.", inputSchema: { type: "object", properties: { projectKey: { type: "string", description: 'The project key (e.g., "PROJ")', }, summary: { type: "string", description: "Brief summary of the issue", }, issueType: { type: "string", description: 'Type of issue (e.g., "Bug", "Task", "Story")', }, description: { type: "string", description: "Detailed description of the issue", }, priority: { type: "string", description: 'Priority name (e.g., "High", "Medium", "Low")', }, }, required: ["projectKey", "summary", "issueType"], }, }, { name: "jira_update_issue", description: "Update fields of an existing Jira issue.", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: 'The issue key (e.g., "PROJ-123")', }, summary: { type: "string", description: "Updated summary", }, description: { type: "string", description: "Updated description", }, priority: { type: "string", description: "Updated priority name", }, }, required: ["issueKey"], }, }, { name: "jira_add_comment", description: "Add a comment to a Jira issue.", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: 'The issue key (e.g., "PROJ-123")', }, comment: { type: "string", description: "The comment text to add", }, }, required: ["issueKey", "comment"], }, }, { name: "jira_get_transitions", description: "Get available transitions for a Jira issue (e.g., what statuses it can move to).", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: 'The issue key (e.g., "PROJ-123")', }, }, required: ["issueKey"], }, }, { name: "jira_transition_issue", description: "Transition a Jira issue to a new status.", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: 'The issue key (e.g., "PROJ-123")', }, transitionId: { type: "string", description: "The ID of the transition to execute (get from jira_get_transitions)", }, }, required: ["issueKey", "transitionId"], }, }, { name: "jira_assign_issue", description: "Assign a Jira issue to a user.", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: 'The issue key (e.g., "PROJ-123")', }, accountId: { type: "string", description: "The account ID of the user to assign (use null to unassign)", }, }, required: ["issueKey", "accountId"], }, }, { name: "jira_get_projects", description: "Get a list of all Jira projects accessible to the authenticated user.", inputSchema: { type: "object", properties: {}, }, }, { name: "jira_search_users", description: "Search for Jira users by name or email.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query (name or email)", }, }, required: ["query"], }, }, { name: "jira_get_comments", description: "Get all comments for a specific Jira issue.", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: 'The issue key (e.g., "PROJ-123")', }, }, required: ["issueKey"], }, }, { name: "jira_get_boards", description: "Get a list of all Jira boards (Scrum and Kanban) accessible to the authenticated user.", inputSchema: { type: "object", properties: { startAt: { type: "number", description: "Index of the first result to return (default: 0)", default: 0, }, maxResults: { type: "number", description: "Maximum number of results to return (default: 50)", default: 50, }, }, }, }, { name: "jira_get_board_issues", description: "Get issues (stories, tasks, bugs, etc.) from a specific Jira board. Use this to get stories based on a board ID.", inputSchema: { type: "object", properties: { boardId: { type: "number", description: "The ID of the board to retrieve issues from", }, startAt: { type: "number", description: "Index of the first result to return (default: 0)", default: 0, }, maxResults: { type: "number", description: "Maximum number of results to return (default: 50)", default: 50, }, jql: { type: "string", description: 'Optional JQL to filter board issues (e.g., "type = Story AND status = "In Progress"")', }, }, required: ["boardId"], }, }, ]; // Initialize MCP server const server = new Server( { name: "jira-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // Handle tool listing server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: TOOLS }; }); // Handle tool execution server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { case "jira_search_issues": { const { jql, maxResults = 50, startAt = 0 } = args as any; const result = await jiraClient.searchIssues(jql, maxResults, startAt); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } case "jira_get_issue": { const { issueKey } = args as any; const issue = await jiraClient.getIssue(issueKey); return { content: [ { type: "text", text: JSON.stringify(issue, null, 2), }, ], }; } case "jira_create_issue": { const { projectKey, summary, issueType, description, priority } = args as any; const additionalFields: any = {}; if (priority) { additionalFields.priority = { name: priority }; } const issue = await jiraClient.createIssue( projectKey, summary, issueType, description, additionalFields ); return { content: [ { type: "text", text: JSON.stringify(issue, null, 2), }, ], }; } case "jira_update_issue": { const { issueKey, summary, description, priority } = args as any; const fields: any = {}; if (summary) fields.summary = summary; if (priority) fields.priority = { name: priority }; if (description) { fields.description = { type: "doc", version: 1, content: [ { type: "paragraph", content: [ { type: "text", text: description, }, ], }, ], }; } await jiraClient.updateIssue(issueKey, fields); return { content: [ { type: "text", text: `Successfully updated issue ${issueKey}`, }, ], }; } case "jira_add_comment": { const { issueKey, comment } = args as any; const result = await jiraClient.addComment(issueKey, comment); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } case "jira_get_transitions": { const { issueKey } = args as any; const transitions = await jiraClient.getTransitions(issueKey); return { content: [ { type: "text", text: JSON.stringify(transitions, null, 2), }, ], }; } case "jira_transition_issue": { const { issueKey, transitionId } = args as any; await jiraClient.transitionIssue(issueKey, transitionId); return { content: [ { type: "text", text: `Successfully transitioned issue ${issueKey}`, }, ], }; } case "jira_assign_issue": { const { issueKey, accountId } = args as any; await jiraClient.assignIssue(issueKey, accountId); return { content: [ { type: "text", text: `Successfully assigned issue ${issueKey}`, }, ], }; } case "jira_get_projects": { const projects = await jiraClient.getProjects(); return { content: [ { type: "text", text: JSON.stringify(projects, null, 2), }, ], }; } case "jira_search_users": { const { query } = args as any; const users = await jiraClient.getUsers(query); return { content: [ { type: "text", text: JSON.stringify(users, null, 2), }, ], }; } case "jira_get_comments": { const { issueKey } = args as any; const comments = await jiraClient.getIssueComments(issueKey); return { content: [ { type: "text", text: JSON.stringify(comments, null, 2), }, ], }; } case "jira_get_boards": { const { startAt = 0, maxResults = 50 } = args as any; const result = await jiraClient.getBoards(startAt, maxResults); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } case "jira_get_board_issues": { const { boardId, startAt = 0, maxResults = 50, jql } = args as any; const result = await jiraClient.getBoardIssues( boardId, startAt, maxResults, jql ); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}\n${ error.response?.data ? JSON.stringify(error.response.data, null, 2) : "" }`, }, ], isError: true, }; } }); // Start the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Jira MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });

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

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