mcp-jira-stdio 1.5.3 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,6 +16,28 @@
16
16
 
17
17
  A Model Context Protocol (MCP) server for Jira API integration. Enables reading, writing, and managing Jira issues and projects directly from your MCP client (e.g., Claude Desktop).
18
18
 
19
+ ## ⚡ Quick Install for Claude Code
20
+
21
+ The fastest way to add this MCP server to Claude Code:
22
+
23
+ ```bash
24
+ claude mcp add jira npx mcp-jira-stdio@latest \
25
+ --env JIRA_BASE_URL=https://yourcompany.atlassian.net \
26
+ --env JIRA_EMAIL=your-email@example.com \
27
+ --env JIRA_API_TOKEN=your-api-token
28
+ ```
29
+
30
+ Replace the values with your actual Jira credentials:
31
+ - **JIRA_BASE_URL**: Your Jira instance URL (e.g., `https://yourcompany.atlassian.net`)
32
+ - **JIRA_EMAIL**: Your Jira account email
33
+ - **JIRA_API_TOKEN**: Your Jira API token ([generate here](https://id.atlassian.com/manage-profile/security/api-tokens))
34
+
35
+ That's it! The server will be automatically configured and ready to use.
36
+
37
+ ### Alternative: Manual Configuration
38
+
39
+ If you prefer to configure manually or use Claude Desktop, see the [Configuration](#6-configure-mcp-client) section below.
40
+
19
41
  ## 🚀 Quick Start
20
42
 
21
43
  ### 1. Prerequisites
@@ -95,6 +117,19 @@ task jira:projects
95
117
 
96
118
  ### 6. Configure MCP Client
97
119
 
120
+ #### For Claude Code
121
+
122
+ Use the quick install command (recommended):
123
+
124
+ ```bash
125
+ claude mcp add jira npx mcp-jira-stdio@latest \
126
+ --env JIRA_BASE_URL=https://yourcompany.atlassian.net \
127
+ --env JIRA_EMAIL=your-email@example.com \
128
+ --env JIRA_API_TOKEN=your-api-token
129
+ ```
130
+
131
+ #### For Claude Desktop
132
+
98
133
  Add to your Claude Desktop config:
99
134
 
100
135
  - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
5
  import axios from 'axios';
6
6
  import { z } from 'zod';
7
+ import mdToAdf from 'md-to-adf';
7
8
 
8
9
  var __create = Object.create;
9
10
  var __defProp = Object.defineProperty;
@@ -439,7 +440,7 @@ var require_package2 = __commonJS({
439
440
  "package.json"(exports, module) {
440
441
  module.exports = {
441
442
  name: "mcp-jira-stdio",
442
- version: "1.5.3",
443
+ version: "1.6.0",
443
444
  description: "Model Context Protocol (MCP) server for Jira API integration",
444
445
  author: "graslt",
445
446
  license: "MIT",
@@ -490,6 +491,7 @@ var require_package2 = __commonJS({
490
491
  dependencies: {
491
492
  "@modelcontextprotocol/sdk": "^1.17.1",
492
493
  axios: "^1.7.0",
494
+ "md-to-adf": "^0.6.4",
493
495
  zod: "^3.22.4"
494
496
  },
495
497
  devDependencies: {
@@ -759,7 +761,10 @@ var CreateIssueInputSchema = z.object({
759
761
  customFields: z.record(z.any()).optional().describe(
760
762
  'Additional Jira field mappings, e.g. { "customfield_12345": value }. Use for required custom fields.'
761
763
  ),
762
- returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after creation")
764
+ returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after creation"),
765
+ format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
766
+ 'Description format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
767
+ )
763
768
  });
764
769
  var UpdateIssueInputSchema = z.object({
765
770
  issueKey: z.string().describe("Issue key to update").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
@@ -769,7 +774,10 @@ var UpdateIssueInputSchema = z.object({
769
774
  assignee: z.string().optional().describe("New assignee account ID"),
770
775
  labels: z.array(z.string()).optional().describe("New labels (replaces existing)"),
771
776
  components: z.array(z.string()).optional().describe("New components (replaces existing)"),
772
- returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after update")
777
+ returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after update"),
778
+ format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
779
+ 'Description format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
780
+ )
773
781
  });
774
782
  var GetMyIssuesInputSchema = z.object({
775
783
  nextPageToken: z.string().optional().describe(
@@ -800,7 +808,10 @@ var AddCommentInputSchema = z.object({
800
808
  visibility: z.object({
801
809
  type: z.enum(["group", "role"]).describe("Visibility type"),
802
810
  value: z.string().describe("Group name or role name")
803
- }).optional().describe("Comment visibility restrictions")
811
+ }).optional().describe("Comment visibility restrictions"),
812
+ format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
813
+ 'Comment format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
814
+ )
804
815
  });
805
816
  var GetProjectInfoInputSchema = z.object({
806
817
  projectKey: z.string().describe("Project key to get detailed information for").refine((v) => isValidProjectKey(v), "Invalid project key format"),
@@ -813,7 +824,10 @@ var CreateSubtaskInputSchema = z.object({
813
824
  priority: z.string().optional().describe("Subtask priority"),
814
825
  assignee: z.string().optional().describe("Assignee account ID"),
815
826
  labels: z.array(z.string()).optional().describe("Subtask labels"),
816
- components: z.array(z.string()).optional().describe("Component names")
827
+ components: z.array(z.string()).optional().describe("Component names"),
828
+ format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
829
+ 'Description format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
830
+ )
817
831
  });
818
832
  var GetCreateMetaInputSchema = z.object({
819
833
  projectKey: z.string().describe("Project key to get create metadata for").refine((v) => isValidProjectKey(v), "Invalid project key format"),
@@ -822,12 +836,21 @@ var GetCreateMetaInputSchema = z.object({
822
836
  var GetCustomFieldsInputSchema = z.object({
823
837
  projectKey: z.string().optional().describe("Project key to filter custom fields (optional)").refine((v) => v ? isValidProjectKey(v) : true, "Invalid project key format")
824
838
  });
825
-
826
- // src/utils/api-helpers.ts
827
- function ensureAdfDescription(desc) {
839
+ function ensureAdfDescription(desc, format = "markdown") {
828
840
  if (!desc) return desc;
829
841
  if (typeof desc === "object") return desc;
830
842
  if (typeof desc !== "string") return desc;
843
+ if (format === "markdown") {
844
+ try {
845
+ return mdToAdf(desc);
846
+ } catch (error) {
847
+ console.warn("Markdown to ADF conversion failed, falling back to plain text:", error);
848
+ format = "plain";
849
+ }
850
+ }
851
+ if (format === "adf") {
852
+ return desc;
853
+ }
831
854
  const urlRegex = /https?:\/\/[^\s)]+/g;
832
855
  const makeTextNodes = (text) => {
833
856
  const nodes = [];
@@ -1010,7 +1033,10 @@ async function createIssue(issueData, options = { returnIssue: true }) {
1010
1033
  issuetype: { name: issueData.issueType }
1011
1034
  };
1012
1035
  if (issueData.description !== void 0) {
1013
- fields.description = ensureAdfDescription(issueData.description);
1036
+ fields.description = ensureAdfDescription(
1037
+ issueData.description,
1038
+ issueData.format || "markdown"
1039
+ );
1014
1040
  }
1015
1041
  if (issueData.priority) {
1016
1042
  fields.priority = { name: issueData.priority };
@@ -1048,7 +1074,7 @@ async function updateIssue(issueKey, updates) {
1048
1074
  fields.summary = updates.summary;
1049
1075
  }
1050
1076
  if (updates.description !== void 0) {
1051
- fields.description = ensureAdfDescription(updates.description);
1077
+ fields.description = ensureAdfDescription(updates.description, updates.format || "markdown");
1052
1078
  }
1053
1079
  if (updates.priority !== void 0) {
1054
1080
  fields.priority = { name: updates.priority };
@@ -1145,8 +1171,8 @@ async function getStatuses(options = {}) {
1145
1171
  }
1146
1172
  return await makeJiraRequest(config);
1147
1173
  }
1148
- async function addComment(issueKey, body, visibility) {
1149
- const adfBody = ensureAdfDescription(body);
1174
+ async function addComment(issueKey, body, visibility, format) {
1175
+ const adfBody = ensureAdfDescription(body, format || "markdown");
1150
1176
  const data = {
1151
1177
  body: adfBody
1152
1178
  };
@@ -1187,7 +1213,10 @@ async function createSubtask(parentIssueKey, subtaskData) {
1187
1213
  issuetype: { id: subtaskType.id }
1188
1214
  };
1189
1215
  if (subtaskData.description !== void 0) {
1190
- fields.description = ensureAdfDescription(subtaskData.description);
1216
+ fields.description = ensureAdfDescription(
1217
+ subtaskData.description,
1218
+ subtaskData.format || "markdown"
1219
+ );
1191
1220
  }
1192
1221
  if (subtaskData.priority) {
1193
1222
  fields.priority = { name: subtaskData.priority };
@@ -2088,7 +2117,7 @@ async function handleGetStatuses(input) {
2088
2117
  var log11 = createLogger("tool:create-issue");
2089
2118
  var createIssueTool = {
2090
2119
  name: TOOL_NAMES.CREATE_ISSUE,
2091
- description: 'Creates a new Jira issue in the specified project. Supports setting issue type, priority, assignee, labels, components, and custom fields. Description accepts plain text and is auto-formatted to ADF: lines ending with ":" become headings, numbered lines create ordered lists, and URLs are linkified. For required custom fields, supply them via customFields (e.g., { "customfield_12345": { id: "..." } }). Returns the created issue with all details.',
2120
+ description: 'Creates a new Jira issue in the specified project. Supports setting issue type, priority, assignee, labels, components, and custom fields. Description format is controlled by the "format" parameter (default: markdown). For required custom fields, supply them via customFields (e.g., { "customfield_12345": { id: "..." } }). Returns the created issue with all details.',
2092
2121
  inputSchema: {
2093
2122
  type: "object",
2094
2123
  properties: {
@@ -2103,7 +2132,7 @@ var createIssueTool = {
2103
2132
  },
2104
2133
  description: {
2105
2134
  anyOf: [{ type: "string" }, { type: "object" }],
2106
- description: "Detailed issue description (optional). Accepts plain text (auto-formatted to ADF) or an ADF document."
2135
+ description: 'Detailed issue description (optional). Format depends on the "format" parameter.'
2107
2136
  },
2108
2137
  issueType: {
2109
2138
  type: "string",
@@ -2138,6 +2167,12 @@ var createIssueTool = {
2138
2167
  type: "boolean",
2139
2168
  description: "If false, returns only the issue key without fetching full details",
2140
2169
  default: true
2170
+ },
2171
+ format: {
2172
+ type: "string",
2173
+ enum: ["markdown", "adf", "plain"],
2174
+ description: 'Description format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2175
+ default: "markdown"
2141
2176
  }
2142
2177
  },
2143
2178
  required: ["projectKey", "summary", "issueType"]
@@ -2158,6 +2193,7 @@ async function handleCreateIssue(input) {
2158
2193
  if (validated.labels !== void 0) createParams.labels = validated.labels;
2159
2194
  if (validated.components !== void 0) createParams.components = validated.components;
2160
2195
  if (validated.customFields !== void 0) createParams.customFields = validated.customFields;
2196
+ if (validated.format !== void 0) createParams.format = validated.format;
2161
2197
  let issueOrKey;
2162
2198
  if (validated.returnIssue === false) {
2163
2199
  issueOrKey = await createIssue(createParams, { returnIssue: false });
@@ -2182,7 +2218,7 @@ async function handleCreateIssue(input) {
2182
2218
  var log12 = createLogger("tool:update-issue");
2183
2219
  var updateIssueTool = {
2184
2220
  name: TOOL_NAMES.UPDATE_ISSUE,
2185
- description: 'Updates an existing Jira issue by its key. Supports updating summary, description, priority, assignee, labels, and components. Description accepts plain text and is auto-formatted to ADF: headings (lines ending with ":"), numbered/bullet lists, and links. Only specified fields will be updated.',
2221
+ description: 'Updates an existing Jira issue by its key. Supports updating summary, description, priority, assignee, labels, and components. Description format is controlled by the "format" parameter (default: markdown). Only specified fields will be updated.',
2186
2222
  inputSchema: {
2187
2223
  type: "object",
2188
2224
  properties: {
@@ -2196,7 +2232,7 @@ var updateIssueTool = {
2196
2232
  },
2197
2233
  description: {
2198
2234
  anyOf: [{ type: "string" }, { type: "object" }],
2199
- description: "New issue description (optional). Accepts plain text (auto-formatted to ADF) or an ADF document."
2235
+ description: 'New issue description (optional). Format depends on the "format" parameter.'
2200
2236
  },
2201
2237
  priority: {
2202
2238
  type: "string",
@@ -2220,6 +2256,12 @@ var updateIssueTool = {
2220
2256
  type: "boolean",
2221
2257
  description: "If false, returns a success message without fetching the updated issue",
2222
2258
  default: true
2259
+ },
2260
+ format: {
2261
+ type: "string",
2262
+ enum: ["markdown", "adf", "plain"],
2263
+ description: 'Description format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2264
+ default: "markdown"
2223
2265
  }
2224
2266
  },
2225
2267
  required: ["issueKey"]
@@ -2236,6 +2278,7 @@ async function handleUpdateIssue(input) {
2236
2278
  if (validated.assignee !== void 0) updateParams.assignee = validated.assignee;
2237
2279
  if (validated.labels !== void 0) updateParams.labels = validated.labels;
2238
2280
  if (validated.components !== void 0) updateParams.components = validated.components;
2281
+ if (validated.format !== void 0) updateParams.format = validated.format;
2239
2282
  await updateIssue(validated.issueKey, updateParams);
2240
2283
  if (validated.returnIssue === false) {
2241
2284
  log12.info(`Updated issue ${validated.issueKey}`);
@@ -2254,7 +2297,7 @@ async function handleUpdateIssue(input) {
2254
2297
  var log13 = createLogger("tool:add-comment");
2255
2298
  var addCommentTool = {
2256
2299
  name: TOOL_NAMES.ADD_COMMENT,
2257
- description: "Adds a comment to an issue. Supports visibility restrictions for groups or roles. Returns the created comment with author details and timestamp.",
2300
+ description: 'Adds a comment to an issue. Supports visibility restrictions for groups or roles. Comment format is controlled by the "format" parameter (default: markdown). Returns the created comment with author details and timestamp.',
2258
2301
  inputSchema: {
2259
2302
  type: "object",
2260
2303
  properties: {
@@ -2264,7 +2307,7 @@ var addCommentTool = {
2264
2307
  },
2265
2308
  body: {
2266
2309
  type: "string",
2267
- description: "Comment body text",
2310
+ description: 'Comment body text. Format depends on the "format" parameter.',
2268
2311
  minLength: 1
2269
2312
  },
2270
2313
  visibility: {
@@ -2282,6 +2325,12 @@ var addCommentTool = {
2282
2325
  }
2283
2326
  },
2284
2327
  required: ["type", "value"]
2328
+ },
2329
+ format: {
2330
+ type: "string",
2331
+ enum: ["markdown", "adf", "plain"],
2332
+ description: 'Comment format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2333
+ default: "markdown"
2285
2334
  }
2286
2335
  },
2287
2336
  required: ["issueKey", "body"]
@@ -2291,7 +2340,12 @@ async function handleAddComment(input) {
2291
2340
  try {
2292
2341
  const validated = validateInput(AddCommentInputSchema, input);
2293
2342
  log13.info(`Adding comment to issue ${validated.issueKey}...`);
2294
- const comment = await addComment(validated.issueKey, validated.body, validated.visibility);
2343
+ const comment = await addComment(
2344
+ validated.issueKey,
2345
+ validated.body,
2346
+ validated.visibility,
2347
+ validated.format
2348
+ );
2295
2349
  log13.info(`Added comment to ${validated.issueKey}`);
2296
2350
  return formatCommentResponse(comment);
2297
2351
  } catch (error) {
@@ -2339,7 +2393,7 @@ async function handleGetProjectInfo(input) {
2339
2393
  var log15 = createLogger("tool:create-subtask");
2340
2394
  var createSubtaskTool = {
2341
2395
  name: TOOL_NAMES.CREATE_SUBTASK,
2342
- description: "Creates a subtask under an existing parent issue. Automatically determines the correct project and subtask issue type. Supports setting priority, assignee, labels, and components.",
2396
+ description: 'Creates a subtask under an existing parent issue. Automatically determines the correct project and subtask issue type. Supports setting priority, assignee, labels, and components. Description format is controlled by the "format" parameter (default: markdown).',
2343
2397
  inputSchema: {
2344
2398
  type: "object",
2345
2399
  properties: {
@@ -2354,7 +2408,7 @@ var createSubtaskTool = {
2354
2408
  },
2355
2409
  description: {
2356
2410
  type: "string",
2357
- description: "Detailed subtask description (optional)"
2411
+ description: 'Detailed subtask description (optional). Format depends on the "format" parameter.'
2358
2412
  },
2359
2413
  priority: {
2360
2414
  type: "string",
@@ -2375,6 +2429,12 @@ var createSubtaskTool = {
2375
2429
  items: { type: "string" },
2376
2430
  description: "Component names (optional)",
2377
2431
  default: []
2432
+ },
2433
+ format: {
2434
+ type: "string",
2435
+ enum: ["markdown", "adf", "plain"],
2436
+ description: 'Description format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2437
+ default: "markdown"
2378
2438
  }
2379
2439
  },
2380
2440
  required: ["parentIssueKey", "summary"]
@@ -2392,6 +2452,7 @@ async function handleCreateSubtask(input) {
2392
2452
  if (validated.assignee !== void 0) subtaskParams.assignee = validated.assignee;
2393
2453
  if (validated.labels !== void 0) subtaskParams.labels = validated.labels;
2394
2454
  if (validated.components !== void 0) subtaskParams.components = validated.components;
2455
+ if (validated.format !== void 0) subtaskParams.format = validated.format;
2395
2456
  const subtask = await createSubtask(validated.parentIssueKey, subtaskParams);
2396
2457
  log15.info(`Created subtask ${subtask.key}`);
2397
2458
  return formatIssueResponse(subtask);