Skip to main content
Glama
index.ts40.1 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import dotenv from "dotenv"; import { JiraClient } from "./jira-client.js"; dotenv.config(); // Validate environment variables const JIRA_BASE_URL = process.env.JIRA_BASE_URL; const PAT = process.env.PAT; if (!JIRA_BASE_URL || !PAT) { console.error( "Missing required environment variables: JIRA_BASE_URL and PAT" ); process.exit(1); } const jiraClient = new JiraClient({ baseUrl: JIRA_BASE_URL, pat: PAT, }); // Tool input schemas const GetIssueSchema = z.object({ issueKey: z.string().describe("The Jira issue key (e.g., PROJ-123)"), expand: z.array(z.string()).optional().describe("Fields to expand"), }); const SearchIssuesSchema = z.object({ jql: z.string().describe("JQL query string"), startAt: z.number().optional().default(0).describe("Starting index"), maxResults: z .number() .optional() .default(50) .describe("Maximum results to return"), fields: z.array(z.string()).optional().describe("Fields to include"), }); const CreateIssueSchema = z.object({ projectKey: z.string().describe("Project key"), summary: z.string().describe("Issue summary"), issueType: z.string().describe("Issue type (e.g., Bug, Task, Story)"), description: z.string().optional().describe("Issue description"), priority: z.string().optional().describe("Priority name"), assignee: z.string().optional().describe("Assignee username"), labels: z.array(z.string()).optional().describe("Labels"), }); const UpdateIssueSchema = z.object({ issueKey: z.string().describe("The Jira issue key"), summary: z.string().optional().describe("New summary"), description: z.string().optional().describe("New description"), priority: z.string().optional().describe("New priority name"), assignee: z.string().optional().describe("New assignee username"), labels: z.array(z.string()).optional().describe("New labels"), }); const AddCommentSchema = z.object({ issueKey: z.string().describe("The Jira issue key"), body: z.string().describe("Comment body"), }); const TransitionIssueSchema = z.object({ issueKey: z.string().describe("The Jira issue key"), transitionId: z.string().describe("Transition ID"), comment: z.string().optional().describe("Optional comment"), }); const AssignIssueSchema = z.object({ issueKey: z.string().describe("The Jira issue key"), assignee: z .string() .nullable() .describe("Username to assign (null to unassign)"), }); const GetCommentsSchema = z.object({ issueKey: z.string().describe("The Jira issue key"), }); const GetTransitionsSchema = z.object({ issueKey: z.string().describe("The Jira issue key"), }); const DeleteIssueSchema = z.object({ issueKey: z.string().describe("The Jira issue key to delete"), }); const GetProjectSchema = z.object({ projectKey: z.string().describe("The project key"), }); const SearchUsersSchema = z.object({ query: z.string().describe("Username search query"), }); const LinkIssuesSchema = z.object({ inwardIssue: z.string().describe("Inward issue key"), outwardIssue: z.string().describe("Outward issue key"), linkType: z.string().describe("Link type name (e.g., 'Blocks', 'Relates')"), }); const AddWatcherSchema = z.object({ issueKey: z.string().describe("The Jira issue key"), username: z.string().describe("Username to add as watcher"), }); const GetCreateMetaSchema = z.object({ projectKey: z.string().describe("Project key to get metadata for"), issueType: z .string() .optional() .describe("Issue type name to filter (e.g., Bug, Task, Story)"), }); const GetEditMetaSchema = z.object({ issueKey: z.string().describe("The Jira issue key to get edit metadata for"), }); const GetProjectVersionsSchema = z.object({ projectKey: z.string().describe("Project key to get versions for"), }); const GetProjectComponentsSchema = z.object({ projectKey: z.string().describe("Project key to get components for"), }); const CreateIssueAdvancedSchema = z.object({ projectKey: z.string().describe("Project key"), summary: z.string().describe("Issue summary"), issueType: z.string().describe("Issue type (e.g., Bug, Task, Story)"), description: z.string().optional().describe("Issue description"), priority: z.string().optional().describe("Priority name"), assignee: z.string().optional().describe("Assignee username"), reporter: z.string().optional().describe("Reporter username"), labels: z.array(z.string()).optional().describe("Labels"), components: z.array(z.string()).optional().describe("Component names"), fixVersions: z.array(z.string()).optional().describe("Fix version names"), affectsVersions: z .array(z.string()) .optional() .describe("Affects version names"), customFields: z .record(z.unknown()) .optional() .describe( 'Custom fields as key-value pairs (e.g., {"customfield_10001": "value"})' ), }); const UpdateIssueAdvancedSchema = z.object({ issueKey: z.string().describe("The Jira issue key"), summary: z.string().optional().describe("New summary"), description: z.string().optional().describe("New description"), priority: z.string().optional().describe("New priority name"), assignee: z.string().optional().describe("New assignee username"), labels: z.array(z.string()).optional().describe("New labels"), components: z.array(z.string()).optional().describe("Component names"), fixVersions: z.array(z.string()).optional().describe("Fix version names"), affectsVersions: z .array(z.string()) .optional() .describe("Affects version names"), customFields: z .record(z.unknown()) .optional() .describe("Custom fields as key-value pairs"), }); const GetFieldOptionsSchema = z.object({ projectKey: z.string().describe("Project key"), issueType: z.string().describe("Issue type name"), fieldKey: z .string() .describe( "Field key (e.g., 'fixVersions', 'components', 'customfield_10001')" ), }); // Create MCP server const server = new Server( { name: "jira-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, resources: {}, }, } ); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "jira_get_issue", description: "Get details of a Jira issue by its key", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key (e.g., PROJ-123)", }, expand: { type: "array", items: { type: "string" }, description: "Fields to expand", }, }, required: ["issueKey"], }, }, { name: "jira_search_issues", description: "Search for Jira issues using JQL", inputSchema: { type: "object", properties: { jql: { type: "string", description: "JQL query string" }, startAt: { type: "number", description: "Starting index" }, maxResults: { type: "number", description: "Maximum results to return", }, fields: { type: "array", items: { type: "string" }, description: "Fields to include", }, }, required: ["jql"], }, }, { name: "jira_create_issue", description: "Create a new Jira issue", inputSchema: { type: "object", properties: { projectKey: { type: "string", description: "Project key" }, summary: { type: "string", description: "Issue summary" }, issueType: { type: "string", description: "Issue type (e.g., Bug, Task, Story)", }, description: { type: "string", description: "Issue description" }, priority: { type: "string", description: "Priority name" }, assignee: { type: "string", description: "Assignee username" }, labels: { type: "array", items: { type: "string" }, description: "Labels", }, }, required: ["projectKey", "summary", "issueType"], }, }, { name: "jira_update_issue", description: "Update an existing Jira issue", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key" }, summary: { type: "string", description: "New summary" }, description: { type: "string", description: "New description" }, priority: { type: "string", description: "New priority name" }, assignee: { type: "string", description: "New assignee username" }, labels: { type: "array", items: { type: "string" }, description: "New labels", }, }, required: ["issueKey"], }, }, { name: "jira_delete_issue", description: "Delete a Jira issue", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key to delete", }, }, required: ["issueKey"], }, }, { name: "jira_assign_issue", description: "Assign or unassign a Jira issue", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key" }, assignee: { type: ["string", "null"], description: "Username to assign (null to unassign)", }, }, required: ["issueKey", "assignee"], }, }, { name: "jira_get_comments", description: "Get comments on a Jira issue", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key" }, }, required: ["issueKey"], }, }, { name: "jira_add_comment", description: "Add a comment to a Jira issue", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key" }, body: { type: "string", description: "Comment body" }, }, required: ["issueKey", "body"], }, }, { name: "jira_get_transitions", description: "Get available transitions for a Jira issue", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key" }, }, required: ["issueKey"], }, }, { name: "jira_transition_issue", description: "Transition a Jira issue to a new status", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key" }, transitionId: { type: "string", description: "Transition ID" }, comment: { type: "string", description: "Optional comment" }, }, required: ["issueKey", "transitionId"], }, }, { name: "jira_get_projects", description: "Get all Jira projects", inputSchema: { type: "object", properties: {}, }, }, { name: "jira_get_project", description: "Get details of a specific Jira project", inputSchema: { type: "object", properties: { projectKey: { type: "string", description: "The project key" }, }, required: ["projectKey"], }, }, { name: "jira_search_users", description: "Search for Jira users", inputSchema: { type: "object", properties: { query: { type: "string", description: "Username search query" }, }, required: ["query"], }, }, { name: "jira_get_current_user", description: "Get the current authenticated user", inputSchema: { type: "object", properties: {}, }, }, { name: "jira_link_issues", description: "Link two Jira issues", inputSchema: { type: "object", properties: { inwardIssue: { type: "string", description: "Inward issue key" }, outwardIssue: { type: "string", description: "Outward issue key" }, linkType: { type: "string", description: "Link type name (e.g., 'Blocks', 'Relates')", }, }, required: ["inwardIssue", "outwardIssue", "linkType"], }, }, { name: "jira_add_watcher", description: "Add a watcher to a Jira issue", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key" }, username: { type: "string", description: "Username to add as watcher", }, }, required: ["issueKey", "username"], }, }, { name: "jira_get_priorities", description: "Get all available priorities", inputSchema: { type: "object", properties: {}, }, }, { name: "jira_get_statuses", description: "Get all available statuses", inputSchema: { type: "object", properties: {}, }, }, { name: "jira_get_create_meta", description: "Get metadata for creating issues - shows required fields and allowed values (dropdown options) for a project and issue type. IMPORTANT: Call this before creating an issue to know what fields are required and what values are allowed.", inputSchema: { type: "object", properties: { projectKey: { type: "string", description: "Project key to get metadata for", }, issueType: { type: "string", description: "Issue type name to filter (e.g., Bug, Task, Story)", }, }, required: ["projectKey"], }, }, { name: "jira_get_edit_meta", description: "Get metadata for editing an issue - shows editable fields and allowed values for an existing issue", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key to get edit metadata for", }, }, required: ["issueKey"], }, }, { name: "jira_get_project_versions", description: "Get all versions for a project - use this to find valid values for fixVersions and affectsVersions fields", inputSchema: { type: "object", properties: { projectKey: { type: "string", description: "Project key to get versions for", }, }, required: ["projectKey"], }, }, { name: "jira_get_project_components", description: "Get all components for a project - use this to find valid values for the components field", inputSchema: { type: "object", properties: { projectKey: { type: "string", description: "Project key to get components for", }, }, required: ["projectKey"], }, }, { name: "jira_get_fields", description: "Get all available fields including custom fields - shows field IDs, names, and types", inputSchema: { type: "object", properties: {}, }, }, { name: "jira_get_field_options", description: "Get allowed values/options for a specific field in a project and issue type context", inputSchema: { type: "object", properties: { projectKey: { type: "string", description: "Project key" }, issueType: { type: "string", description: "Issue type name" }, fieldKey: { type: "string", description: "Field key (e.g., 'fixVersions', 'components', 'customfield_10001')", }, }, required: ["projectKey", "issueType", "fieldKey"], }, }, { name: "jira_get_issue_link_types", description: "Get all available issue link types", inputSchema: { type: "object", properties: {}, }, }, { name: "jira_create_issue_advanced", description: "Create a new Jira issue with full field support including fixVersions, components, and custom fields. Use jira_get_create_meta first to discover required fields and allowed values.", inputSchema: { type: "object", properties: { projectKey: { type: "string", description: "Project key" }, summary: { type: "string", description: "Issue summary" }, issueType: { type: "string", description: "Issue type (e.g., Bug, Task, Story)", }, description: { type: "string", description: "Issue description" }, priority: { type: "string", description: "Priority name" }, assignee: { type: "string", description: "Assignee username" }, reporter: { type: "string", description: "Reporter username" }, labels: { type: "array", items: { type: "string" }, description: "Labels", }, components: { type: "array", items: { type: "string" }, description: "Component names", }, fixVersions: { type: "array", items: { type: "string" }, description: "Fix version names", }, affectsVersions: { type: "array", items: { type: "string" }, description: "Affects version names", }, customFields: { type: "object", description: 'Custom fields as key-value pairs (e.g., {"customfield_10001": "value"})', }, }, required: ["projectKey", "summary", "issueType"], }, }, { name: "jira_update_issue_advanced", description: "Update a Jira issue with full field support including fixVersions, components, and custom fields. Use jira_get_edit_meta first to discover editable fields and allowed values.", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "The Jira issue key" }, summary: { type: "string", description: "New summary" }, description: { type: "string", description: "New description" }, priority: { type: "string", description: "New priority name" }, assignee: { type: "string", description: "New assignee username" }, labels: { type: "array", items: { type: "string" }, description: "New labels", }, components: { type: "array", items: { type: "string" }, description: "Component names", }, fixVersions: { type: "array", items: { type: "string" }, description: "Fix version names", }, affectsVersions: { type: "array", items: { type: "string" }, description: "Affects version names", }, customFields: { type: "object", description: "Custom fields as key-value pairs", }, }, required: ["issueKey"], }, }, ], }; }); // List available resources server.setRequestHandler(ListResourcesRequestSchema, async () => { try { // Fetch projects to create dynamic resources const projects = await jiraClient.getProjects(); const currentUser = await jiraClient.getCurrentUser(); const resources = [ // Static resources { uri: "jira://config", name: "Jira Configuration", description: "Current Jira server configuration and connection info", mimeType: "application/json", }, { uri: "jira://current-user", name: "Current User", description: "Currently authenticated Jira user", mimeType: "application/json", }, { uri: "jira://priorities", name: "Priorities", description: "All available issue priorities", mimeType: "application/json", }, { uri: "jira://statuses", name: "Statuses", description: "All available issue statuses", mimeType: "application/json", }, { uri: "jira://fields", name: "Fields", description: "All available fields including custom fields", mimeType: "application/json", }, { uri: "jira://link-types", name: "Issue Link Types", description: "All available issue link types", mimeType: "application/json", }, { uri: "jira://projects", name: "All Projects", description: `List of all ${projects.length} Jira projects`, mimeType: "application/json", }, // Dynamic project resources (top 20 projects) ...projects.slice(0, 20).map((project) => ({ uri: `jira://project/${project.key}`, name: `Project: ${project.key}`, description: `${project.name} - versions, components, and issue types`, mimeType: "application/json", })), // My issues resource { uri: `jira://my-issues`, name: "My Issues", description: `Issues assigned to ${currentUser.displayName}`, mimeType: "application/json", }, ]; return { resources }; } catch (error) { return { resources: [ { uri: "jira://config", name: "Jira Configuration", description: "Current Jira server configuration", mimeType: "application/json", }, ], }; } }); // Read resource content server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; try { // Parse the URI if (uri === "jira://config") { const config = { baseUrl: JIRA_BASE_URL, serverInfo: "Self-hosted Jira Server", authenticated: true, }; return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(config, null, 2), }, ], }; } if (uri === "jira://current-user") { const user = await jiraClient.getCurrentUser(); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(user, null, 2), }, ], }; } if (uri === "jira://priorities") { const priorities = await jiraClient.getPriorities(); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(priorities, null, 2), }, ], }; } if (uri === "jira://statuses") { const statuses = await jiraClient.getStatuses(); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(statuses, null, 2), }, ], }; } if (uri === "jira://fields") { const fields = await jiraClient.getFields(); // Group fields by type for better readability const grouped = { system: fields.filter((f) => !f.custom), custom: fields.filter((f) => f.custom), }; return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(grouped, null, 2), }, ], }; } if (uri === "jira://link-types") { const linkTypes = await jiraClient.getIssueLinkTypes(); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(linkTypes, null, 2), }, ], }; } if (uri === "jira://projects") { const projects = await jiraClient.getProjects(); const summary = projects.map((p) => ({ key: p.key, name: p.name, projectTypeKey: p.projectTypeKey, })); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(summary, null, 2), }, ], }; } if (uri === "jira://my-issues") { const results = await jiraClient.searchIssues( "assignee = currentUser() ORDER BY updated DESC", 0, 50 ); const summary = { total: results.total, issues: results.issues.map((i) => ({ key: i.key, summary: i.fields.summary, status: i.fields.status.name, priority: i.fields.priority?.name, updated: i.fields.updated, })), }; return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(summary, null, 2), }, ], }; } // Handle project-specific resources: jira://project/{key} const projectMatch = uri.match(/^jira:\/\/project\/([A-Z0-9]+)$/); if (projectMatch) { const projectKey = projectMatch[1]; const [project, versions, components, issueTypes] = await Promise.all([ jiraClient.getProject(projectKey), jiraClient.getProjectVersions(projectKey), jiraClient.getProjectComponents(projectKey), jiraClient.getCreateMetaIssueTypes(projectKey), ]); const projectInfo = { key: project.key, name: project.name, lead: project.lead, versions: versions.map((v) => ({ name: v.name, released: v.released, archived: v.archived, })), components: components.map((c) => ({ name: c.name, description: c.description, })), issueTypes: issueTypes.values.map((t) => ({ id: t.id, name: t.name, subtask: t.subtask, })), }; return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(projectInfo, null, 2), }, ], }; } throw new McpError(ErrorCode.InvalidRequest, `Unknown resource: ${uri}`); } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `Error reading resource ${uri}: ${ error instanceof Error ? error.message : String(error) }` ); } }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "jira_get_issue": { const { issueKey, expand } = GetIssueSchema.parse(args); const issue = await jiraClient.getIssue(issueKey, expand); return { content: [{ type: "text", text: JSON.stringify(issue, null, 2) }], }; } case "jira_search_issues": { const { jql, startAt, maxResults, fields } = SearchIssuesSchema.parse(args); const results = await jiraClient.searchIssues( jql, startAt, maxResults, fields ); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }], }; } case "jira_create_issue": { const { projectKey, summary, issueType, description, priority, assignee, labels, } = CreateIssueSchema.parse(args); const issue = await jiraClient.createIssue({ fields: { project: { key: projectKey }, summary, issuetype: { name: issueType }, ...(description && { description }), ...(priority && { priority: { name: priority } }), ...(assignee && { assignee: { name: assignee } }), ...(labels && { labels }), }, }); return { content: [{ type: "text", text: JSON.stringify(issue, null, 2) }], }; } case "jira_update_issue": { const { issueKey, summary, description, priority, assignee, labels } = UpdateIssueSchema.parse(args); await jiraClient.updateIssue(issueKey, { fields: { ...(summary && { summary }), ...(description && { description }), ...(priority && { priority: { name: priority } }), ...(assignee && { assignee: { name: assignee } }), ...(labels && { labels }), }, }); return { content: [ { type: "text", text: `Issue ${issueKey} updated successfully` }, ], }; } case "jira_delete_issue": { const { issueKey } = DeleteIssueSchema.parse(args); await jiraClient.deleteIssue(issueKey); return { content: [ { type: "text", text: `Issue ${issueKey} deleted successfully` }, ], }; } case "jira_assign_issue": { const { issueKey, assignee } = AssignIssueSchema.parse(args); await jiraClient.assignIssue(issueKey, assignee); return { content: [ { type: "text", text: assignee ? `Issue ${issueKey} assigned to ${assignee}` : `Issue ${issueKey} unassigned`, }, ], }; } case "jira_get_comments": { const { issueKey } = GetCommentsSchema.parse(args); const comments = await jiraClient.getComments(issueKey); return { content: [{ type: "text", text: JSON.stringify(comments, null, 2) }], }; } case "jira_add_comment": { const { issueKey, body } = AddCommentSchema.parse(args); const comment = await jiraClient.addComment(issueKey, body); return { content: [{ type: "text", text: JSON.stringify(comment, null, 2) }], }; } case "jira_get_transitions": { const { issueKey } = GetTransitionsSchema.parse(args); const transitions = await jiraClient.getTransitions(issueKey); return { content: [ { type: "text", text: JSON.stringify(transitions, null, 2) }, ], }; } case "jira_transition_issue": { const { issueKey, transitionId, comment } = TransitionIssueSchema.parse(args); await jiraClient.transitionIssue(issueKey, transitionId, comment); return { content: [ { type: "text", text: `Issue ${issueKey} transitioned successfully`, }, ], }; } case "jira_get_projects": { const projects = await jiraClient.getProjects(); return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }], }; } case "jira_get_project": { const { projectKey } = GetProjectSchema.parse(args); const project = await jiraClient.getProject(projectKey); return { content: [{ type: "text", text: JSON.stringify(project, null, 2) }], }; } case "jira_search_users": { const { query } = SearchUsersSchema.parse(args); const users = await jiraClient.searchUsers(query); return { content: [{ type: "text", text: JSON.stringify(users, null, 2) }], }; } case "jira_get_current_user": { const user = await jiraClient.getCurrentUser(); return { content: [{ type: "text", text: JSON.stringify(user, null, 2) }], }; } case "jira_link_issues": { const { inwardIssue, outwardIssue, linkType } = LinkIssuesSchema.parse(args); await jiraClient.linkIssues(inwardIssue, outwardIssue, linkType); return { content: [ { type: "text", text: `Issues ${inwardIssue} and ${outwardIssue} linked with type "${linkType}"`, }, ], }; } case "jira_add_watcher": { const { issueKey, username } = AddWatcherSchema.parse(args); await jiraClient.addWatcher(issueKey, username); return { content: [ { type: "text", text: `Added ${username} as watcher to ${issueKey}`, }, ], }; } case "jira_get_priorities": { const priorities = await jiraClient.getPriorities(); return { content: [ { type: "text", text: JSON.stringify(priorities, null, 2) }, ], }; } case "jira_get_statuses": { const statuses = await jiraClient.getStatuses(); return { content: [{ type: "text", text: JSON.stringify(statuses, null, 2) }], }; } case "jira_get_create_meta": { const { projectKey, issueType } = GetCreateMetaSchema.parse(args); const meta = await jiraClient.getCreateMeta(projectKey, issueType); return { content: [{ type: "text", text: JSON.stringify(meta, null, 2) }], }; } case "jira_get_edit_meta": { const { issueKey } = GetEditMetaSchema.parse(args); const meta = await jiraClient.getEditMeta(issueKey); return { content: [{ type: "text", text: JSON.stringify(meta, null, 2) }], }; } case "jira_get_project_versions": { const { projectKey } = GetProjectVersionsSchema.parse(args); const versions = await jiraClient.getProjectVersions(projectKey); return { content: [{ type: "text", text: JSON.stringify(versions, null, 2) }], }; } case "jira_get_project_components": { const { projectKey } = GetProjectComponentsSchema.parse(args); const components = await jiraClient.getProjectComponents(projectKey); return { content: [ { type: "text", text: JSON.stringify(components, null, 2) }, ], }; } case "jira_get_fields": { const fields = await jiraClient.getFields(); return { content: [{ type: "text", text: JSON.stringify(fields, null, 2) }], }; } case "jira_get_field_options": { const { projectKey, issueType, fieldKey } = GetFieldOptionsSchema.parse(args); const options = await jiraClient.getFieldOptions( projectKey, issueType, fieldKey ); return { content: [{ type: "text", text: JSON.stringify(options, null, 2) }], }; } case "jira_get_issue_link_types": { const linkTypes = await jiraClient.getIssueLinkTypes(); return { content: [{ type: "text", text: JSON.stringify(linkTypes, null, 2) }], }; } case "jira_create_issue_advanced": { const { projectKey, summary, issueType, description, priority, assignee, reporter, labels, components, fixVersions, affectsVersions, customFields, } = CreateIssueAdvancedSchema.parse(args); const fields: Record<string, unknown> = { project: { key: projectKey }, summary, issuetype: { name: issueType }, }; if (description) fields.description = description; if (priority) fields.priority = { name: priority }; if (assignee) fields.assignee = { name: assignee }; if (reporter) fields.reporter = { name: reporter }; if (labels) fields.labels = labels; if (components) fields.components = components.map((name) => ({ name })); if (fixVersions) fields.fixVersions = fixVersions.map((name) => ({ name })); if (affectsVersions) fields.versions = affectsVersions.map((name) => ({ name })); // Merge custom fields if (customFields) { Object.assign(fields, customFields); } const issue = await jiraClient.createIssueRaw(fields); return { content: [{ type: "text", text: JSON.stringify(issue, null, 2) }], }; } case "jira_update_issue_advanced": { const { issueKey, summary, description, priority, assignee, labels, components, fixVersions, affectsVersions, customFields, } = UpdateIssueAdvancedSchema.parse(args); const fields: Record<string, unknown> = {}; if (summary) fields.summary = summary; if (description) fields.description = description; if (priority) fields.priority = { name: priority }; if (assignee) fields.assignee = { name: assignee }; if (labels) fields.labels = labels; if (components) fields.components = components.map((name) => ({ name })); if (fixVersions) fields.fixVersions = fixVersions.map((name) => ({ name })); if (affectsVersions) fields.versions = affectsVersions.map((name) => ({ name })); // Merge custom fields if (customFields) { Object.assign(fields, customFields); } await jiraClient.updateIssueRaw(issueKey, fields); return { content: [ { type: "text", text: `Issue ${issueKey} updated successfully` }, ], }; } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof z.ZodError) { throw new McpError( ErrorCode.InvalidParams, `Invalid parameters: ${error.errors.map((e) => e.message).join(", ")}` ); } if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `Error executing ${name}: ${ error instanceof Error ? error.message : String(error) }` ); } }); // 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); });

Implementation Reference

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/yogeshhrathod/JiraMCP'

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