mcp-jira-stdio 1.8.1 → 1.10.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/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
4
  import axios from 'axios';
6
5
  import { z } from 'zod';
7
6
  import FormData from 'form-data';
@@ -441,7 +440,7 @@ var require_package2 = __commonJS({
441
440
  "package.json"(exports$1, module) {
442
441
  module.exports = {
443
442
  name: "mcp-jira-stdio",
444
- version: "1.8.1",
443
+ version: "1.10.0",
445
444
  description: "Model Context Protocol (MCP) server for Jira API integration",
446
445
  author: "graslt",
447
446
  license: "MIT",
@@ -490,14 +489,14 @@ var require_package2 = __commonJS({
490
489
  node: ">=20.0.0"
491
490
  },
492
491
  dependencies: {
493
- "@modelcontextprotocol/sdk": "^1.17.1",
492
+ "@modelcontextprotocol/sdk": "^1.29.0",
494
493
  axios: "^1.7.0",
495
494
  "form-data": "^4.0.5",
496
495
  "md-to-adf": "^0.5.0",
497
496
  zod: "^3.22.4"
498
497
  },
499
498
  devDependencies: {
500
- "@types/node": "^22.5.0",
499
+ "@types/node": "^25.3.3",
501
500
  "@typescript-eslint/eslint-plugin": "^8.0.0",
502
501
  "@typescript-eslint/parser": "^8.0.0",
503
502
  "@vitest/coverage-v8": "^4.0.10",
@@ -577,7 +576,10 @@ var TOOL_NAMES = {
577
576
  GET_COMMENTS: "jira_get_comments",
578
577
  ADD_ATTACHMENT: "jira_add_attachment",
579
578
  GET_ATTACHMENTS: "jira_get_attachments",
580
- DELETE_ATTACHMENT: "jira_delete_attachment"
579
+ DELETE_ATTACHMENT: "jira_delete_attachment",
580
+ GET_TRANSITIONS: "jira_get_transitions",
581
+ TRANSITION_ISSUE: "jira_transition_issue",
582
+ GET_ISSUE_GRAPH: "jira_get_issue_graph"
581
583
  };
582
584
 
583
585
  // src/utils/logger.ts
@@ -883,6 +885,9 @@ var UpdateIssueInputSchema = z.object({
883
885
  assignee: z.string().optional().describe("New assignee account ID"),
884
886
  labels: z.array(z.string()).optional().describe("New labels (replaces existing)"),
885
887
  components: z.array(z.string()).optional().describe("New components (replaces existing)"),
888
+ parent: z.string().optional().describe(
889
+ "New parent issue key (e.g., PROJECT-100). Set to empty string to remove the parent."
890
+ ),
886
891
  returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after update"),
887
892
  format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
888
893
  'Description format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
@@ -985,6 +990,37 @@ var GetAttachmentsInputSchema = z.object({
985
990
  var DeleteAttachmentInputSchema = z.object({
986
991
  attachmentId: z.string().min(1).describe("ID of the attachment to delete")
987
992
  });
993
+ var GetTransitionsInputSchema = z.object({
994
+ issueKey: z.string().describe("Issue key to get available transitions for (e.g., PROJECT-123)").refine((v) => isValidIssueKey(v), "Invalid issue key format")
995
+ });
996
+ var TransitionIssueInputSchema = z.object({
997
+ issueKey: z.string().describe("Issue key to transition (e.g., PROJECT-123)").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
998
+ transitionId: z.string().optional().describe(
999
+ "ID of the transition to perform. Use jira_get_transitions to find available transition IDs."
1000
+ ),
1001
+ transitionName: z.string().optional().describe(
1002
+ 'Name of the transition to perform (e.g., "In Progress", "Done"). Case-insensitive. Alternative to transitionId.'
1003
+ ),
1004
+ comment: z.string().optional().describe("Optional comment to add when transitioning the issue"),
1005
+ resolution: z.string().optional().describe(
1006
+ 'Resolution name when transitioning to a resolved/done status (e.g., "Done", "Fixed")'
1007
+ ),
1008
+ format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
1009
+ 'Comment format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
1010
+ )
1011
+ }).refine((data) => data.transitionId || data.transitionName, {
1012
+ message: "Either transitionId or transitionName must be provided"
1013
+ });
1014
+ var GetIssueGraphInputSchema = z.object({
1015
+ issueKey: z.string().min(1).describe("Seed issue key to start graph traversal from (e.g., PROJECT-123)").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
1016
+ maxDepth: z.number().min(1).max(5).default(2).describe("Maximum BFS traversal depth from seed issue (default: 2, max: 5)"),
1017
+ maxNodes: z.number().min(1).max(200).default(50).describe("Maximum number of nodes to include in the graph (default: 50, max: 200)"),
1018
+ includeHierarchy: z.boolean().default(true).describe("Include parent/child/subtask edges (default: true)"),
1019
+ linkTypes: z.array(z.string()).optional().describe(
1020
+ 'Filter to specific link types (e.g., ["Blocks", "Relates"]). If omitted, includes all link types.'
1021
+ ),
1022
+ direction: z.enum(["all", "inward", "outward"]).default("all").describe('Which link directions to follow: "all", "inward", or "outward" (default: "all")')
1023
+ });
988
1024
  function ensureAdfDescription(desc, format = "markdown") {
989
1025
  if (!desc) return desc;
990
1026
  if (typeof desc === "object") return desc;
@@ -1237,6 +1273,9 @@ async function updateIssue(issueKey, updates) {
1237
1273
  if (updates.components !== void 0) {
1238
1274
  fields.components = updates.components.map((name) => ({ name }));
1239
1275
  }
1276
+ if (updates.parent !== void 0) {
1277
+ fields.parent = updates.parent ? { key: updates.parent } : null;
1278
+ }
1240
1279
  const config = {
1241
1280
  method: "PUT",
1242
1281
  url: `/issue/${issueKey}`,
@@ -1521,6 +1560,42 @@ async function deleteAttachment(attachmentId) {
1521
1560
  };
1522
1561
  await makeJiraRequest(config);
1523
1562
  }
1563
+ async function getTransitions(issueKey) {
1564
+ const config = {
1565
+ method: "GET",
1566
+ url: `/issue/${issueKey}/transitions`
1567
+ };
1568
+ const response = await makeJiraRequest(config);
1569
+ return response.transitions;
1570
+ }
1571
+ async function transitionIssue(issueKey, transitionId, options = {}) {
1572
+ const data = {
1573
+ transition: { id: transitionId }
1574
+ };
1575
+ if (options.resolution) {
1576
+ data.fields = {
1577
+ resolution: { name: options.resolution }
1578
+ };
1579
+ }
1580
+ if (options.comment) {
1581
+ const adfBody = ensureAdfDescription(options.comment, options.format || "markdown");
1582
+ data.update = {
1583
+ comment: [
1584
+ {
1585
+ add: {
1586
+ body: adfBody
1587
+ }
1588
+ }
1589
+ ]
1590
+ };
1591
+ }
1592
+ const config = {
1593
+ method: "POST",
1594
+ url: `/issue/${issueKey}/transitions`,
1595
+ data
1596
+ };
1597
+ await makeJiraRequest(config);
1598
+ }
1524
1599
 
1525
1600
  // src/utils/redaction.ts
1526
1601
  function isRedactionEnabled() {
@@ -1608,6 +1683,8 @@ function formatIssueResponse(issue) {
1608
1683
  }
1609
1684
  const createdText = fields?.created ? new Date(fields.created).toISOString() : "N/A";
1610
1685
  const updatedText = fields?.updated ? new Date(fields.updated).toISOString() : "N/A";
1686
+ const parentText = fields?.parent ? `
1687
+ **Parent:** ${fields.parent.key}${fields.parent.fields?.summary ? ` - ${fields.parent.fields.summary}` : ""}${fields.parent.fields?.status ? ` (${fields.parent.fields.status.name})` : ""}` : "";
1611
1688
  return {
1612
1689
  content: [
1613
1690
  {
@@ -1621,7 +1698,7 @@ function formatIssueResponse(issue) {
1621
1698
  **Project:** ${fields?.project?.name || "Unknown"} (${fields?.project?.key || "N/A"})
1622
1699
  **Issue Type:** ${fields?.issuetype?.name || "Unknown"}
1623
1700
  **Labels:** ${labelsText}
1624
- **Components:** ${componentsText}
1701
+ **Components:** ${componentsText}${parentText}
1625
1702
  **Created:** ${createdText}
1626
1703
  **Updated:** ${updatedText}
1627
1704
 
@@ -2062,6 +2139,34 @@ ${attachmentsList}
2062
2139
  ]
2063
2140
  };
2064
2141
  }
2142
+ function formatTransitionsResponse(transitions, issueKey) {
2143
+ if (transitions.length === 0) {
2144
+ return {
2145
+ content: [
2146
+ {
2147
+ type: "text",
2148
+ text: `No transitions available for issue ${issueKey}. The issue may be in a final state or you may lack permission.`
2149
+ }
2150
+ ]
2151
+ };
2152
+ }
2153
+ const transitionsList = transitions.map(
2154
+ (t) => `\u2022 **${t.name}** (ID: ${t.id})
2155
+ Target status: ${t.to.name} (Category: ${t.to.statusCategory?.name || "Unknown"})`
2156
+ ).join("\n\n");
2157
+ return {
2158
+ content: [
2159
+ {
2160
+ type: "text",
2161
+ text: `**Available transitions for ${issueKey}** (${transitions.length}):
2162
+
2163
+ ${transitionsList}
2164
+
2165
+ \u{1F4A1} **Tip:** Use \`jira_transition_issue\` with the transition ID or name to move this issue.`
2166
+ }
2167
+ ]
2168
+ };
2169
+ }
2065
2170
 
2066
2171
  // src/utils/error-handler.ts
2067
2172
  var log2 = createLogger("error-handler");
@@ -2114,27 +2219,7 @@ function handleError(error) {
2114
2219
  // src/tools/get-visible-projects.ts
2115
2220
  var log3 = createLogger("tool:get-visible-projects");
2116
2221
  var getVisibleProjectsTool = {
2117
- name: TOOL_NAMES.GET_VISIBLE_PROJECTS,
2118
- description: "Retrieves all projects accessible to the authenticated user. Returns project keys, names, descriptions, and basic metadata.",
2119
- inputSchema: {
2120
- type: "object",
2121
- properties: {
2122
- expand: {
2123
- type: "array",
2124
- items: { type: "string" },
2125
- description: 'Additional project details to include (e.g., ["description", "lead", "issueTypes"])',
2126
- default: []
2127
- },
2128
- recent: {
2129
- type: "number",
2130
- description: "Limit results to recently accessed projects (max number)",
2131
- minimum: 1,
2132
- maximum: 100
2133
- }
2134
- },
2135
- required: []
2136
- }
2137
- };
2222
+ description: "Retrieves all projects accessible to the authenticated user. Returns project keys, names, descriptions, and basic metadata."};
2138
2223
  async function handleGetVisibleProjects(input) {
2139
2224
  try {
2140
2225
  const validated = validateInput(GetVisibleProjectsInputSchema, input);
@@ -2154,30 +2239,7 @@ async function handleGetVisibleProjects(input) {
2154
2239
  // src/tools/get-issue.ts
2155
2240
  var log4 = createLogger("tool:get-issue");
2156
2241
  var getIssueTool = {
2157
- name: TOOL_NAMES.GET_ISSUE,
2158
- description: 'Retrieve details for a specific Jira issue by key or URL. Use this when the user mentions an issue like "PAYWALL-943" or pastes a Jira link (e.g., https://your.atlassian.net/browse/PAYWALL-943). Returns status, assignee, priority, project, type, labels, components, timestamps, and description.',
2159
- inputSchema: {
2160
- type: "object",
2161
- properties: {
2162
- issueKey: {
2163
- type: "string",
2164
- description: "Issue key or full Jira URL (e.g., PROJECT-123 or https://your.atlassian.net/browse/PROJECT-123)"
2165
- },
2166
- expand: {
2167
- type: "array",
2168
- items: { type: "string" },
2169
- description: 'Additional issue details to include (e.g., ["comments", "attachments", "changelog"])',
2170
- default: []
2171
- },
2172
- fields: {
2173
- type: "array",
2174
- items: { type: "string" },
2175
- description: 'Specific fields to retrieve (e.g., ["summary", "status", "assignee"])'
2176
- }
2177
- },
2178
- required: ["issueKey"]
2179
- }
2180
- };
2242
+ description: 'Retrieve details for a specific Jira issue by key or URL. Use this when the user mentions an issue like "PAYWALL-943" or pastes a Jira link (e.g., https://your.atlassian.net/browse/PAYWALL-943). Returns status, assignee, priority, project, type, labels, components, timestamps, and description.'};
2181
2243
  async function handleGetIssue(input) {
2182
2244
  try {
2183
2245
  const validated = validateInput(GetIssueInputSchema, input);
@@ -2199,41 +2261,7 @@ async function handleGetIssue(input) {
2199
2261
  // src/tools/search-issues.ts
2200
2262
  var log5 = createLogger("tool:search-issues");
2201
2263
  var searchIssuesTool = {
2202
- name: TOOL_NAMES.SEARCH_ISSUES,
2203
- description: 'Search for Jira issues using JQL. Supports complex queries with pagination and field selection. Examples: "project = PROJECT AND status = Open", "assignee = currentUser()". For pagination, use nextPageToken from previous response.',
2204
- inputSchema: {
2205
- type: "object",
2206
- properties: {
2207
- jql: {
2208
- type: "string",
2209
- description: 'JQL query string (e.g., "project = PROJECT AND status = Open")'
2210
- },
2211
- nextPageToken: {
2212
- type: "string",
2213
- description: "Token for pagination. Omit for first page, use value from previous response for next page."
2214
- },
2215
- maxResults: {
2216
- type: "number",
2217
- description: "Maximum number of results to return per page",
2218
- minimum: 1,
2219
- maximum: 100,
2220
- default: 50
2221
- },
2222
- fields: {
2223
- type: "array",
2224
- items: { type: "string" },
2225
- description: "Specific fields to retrieve for each issue"
2226
- },
2227
- expand: {
2228
- type: "array",
2229
- items: { type: "string" },
2230
- description: "Additional details to include for each issue",
2231
- default: []
2232
- }
2233
- },
2234
- required: ["jql"]
2235
- }
2236
- };
2264
+ description: 'Search for Jira issues using JQL. Supports complex queries with pagination and field selection. Examples: "project = PROJECT AND status = Open", "assignee = currentUser()". For pagination, use nextPageToken from previous response.'};
2237
2265
  async function handleSearchIssues(input) {
2238
2266
  try {
2239
2267
  const validated = validateInput(SearchIssuesInputSchema, input);
@@ -2257,37 +2285,7 @@ async function handleSearchIssues(input) {
2257
2285
  // src/tools/get-my-issues.ts
2258
2286
  var log6 = createLogger("tool:get-my-issues");
2259
2287
  var getMyIssuesTool = {
2260
- name: TOOL_NAMES.GET_MY_ISSUES,
2261
- description: "Retrieves issues assigned to current user, sorted by most recently updated first. Supports pagination and field selection. For pagination, use nextPageToken from previous response.",
2262
- inputSchema: {
2263
- type: "object",
2264
- properties: {
2265
- nextPageToken: {
2266
- type: "string",
2267
- description: "Token for pagination. Omit for first page, use value from previous response for next page."
2268
- },
2269
- maxResults: {
2270
- type: "number",
2271
- description: "Maximum number of results to return per page",
2272
- minimum: 1,
2273
- maximum: 100,
2274
- default: 50
2275
- },
2276
- fields: {
2277
- type: "array",
2278
- items: { type: "string" },
2279
- description: "Specific fields to retrieve for each issue"
2280
- },
2281
- expand: {
2282
- type: "array",
2283
- items: { type: "string" },
2284
- description: "Additional details to include for each issue",
2285
- default: []
2286
- }
2287
- },
2288
- required: []
2289
- }
2290
- };
2288
+ description: "Retrieves issues assigned to current user, sorted by most recently updated first. Supports pagination and field selection. For pagination, use nextPageToken from previous response."};
2291
2289
  async function handleGetMyIssues(input) {
2292
2290
  try {
2293
2291
  const validated = validateInput(GetMyIssuesInputSchema, input);
@@ -2311,19 +2309,7 @@ async function handleGetMyIssues(input) {
2311
2309
  // src/tools/get-issue-types.ts
2312
2310
  var log7 = createLogger("tool:get-issue-types");
2313
2311
  var getIssueTypesTool = {
2314
- name: TOOL_NAMES.GET_ISSUE_TYPES,
2315
- description: "Retrieves available issue types. Can get global issue types or project-specific issue types including regular issues and subtasks (Bug, Story, Task, Epic, etc.).",
2316
- inputSchema: {
2317
- type: "object",
2318
- properties: {
2319
- projectKey: {
2320
- type: "string",
2321
- description: "Project key to get issue types for specific project (optional - if not provided, returns global issue types)"
2322
- }
2323
- },
2324
- required: []
2325
- }
2326
- };
2312
+ description: "Retrieves available issue types. Can get global issue types or project-specific issue types including regular issues and subtasks (Bug, Story, Task, Epic, etc.)."};
2327
2313
  async function handleGetIssueTypes(input) {
2328
2314
  try {
2329
2315
  const validated = validateInput(GetIssueTypesInputSchema, input);
@@ -2344,40 +2330,7 @@ async function handleGetIssueTypes(input) {
2344
2330
  // src/tools/get-users.ts
2345
2331
  var log8 = createLogger("tool:get-users");
2346
2332
  var getUsersTool = {
2347
- name: TOOL_NAMES.GET_USERS,
2348
- description: "Search for users by name, email, username, or account ID. Returns display name, email, account status, and account type. Supports pagination.",
2349
- inputSchema: {
2350
- type: "object",
2351
- properties: {
2352
- query: {
2353
- type: "string",
2354
- description: "Search query for user name or email (partial matches supported)"
2355
- },
2356
- username: {
2357
- type: "string",
2358
- description: "Specific username to search for"
2359
- },
2360
- accountId: {
2361
- type: "string",
2362
- description: "Specific account ID to search for"
2363
- },
2364
- startAt: {
2365
- type: "number",
2366
- description: "Index of first result to return (for pagination)",
2367
- minimum: 0,
2368
- default: 0
2369
- },
2370
- maxResults: {
2371
- type: "number",
2372
- description: "Maximum number of results to return",
2373
- minimum: 1,
2374
- maximum: 50,
2375
- default: 50
2376
- }
2377
- },
2378
- required: []
2379
- }
2380
- };
2333
+ description: "Search for users by name, email, username, or account ID. Returns display name, email, account status, and account type. Supports pagination."};
2381
2334
  async function handleGetUsers(input) {
2382
2335
  try {
2383
2336
  const validated = validateInput(GetUsersInputSchema, input);
@@ -2408,14 +2361,7 @@ async function handleGetUsers(input) {
2408
2361
  // src/tools/get-priorities.ts
2409
2362
  var log9 = createLogger("tool:get-priorities");
2410
2363
  var getPrioritiesTool = {
2411
- name: TOOL_NAMES.GET_PRIORITIES,
2412
- description: "Retrieves available priorities (e.g., Highest, High, Medium, Low, Lowest). Returns IDs, names, and descriptions.",
2413
- inputSchema: {
2414
- type: "object",
2415
- properties: {},
2416
- required: []
2417
- }
2418
- };
2364
+ description: "Retrieves available priorities (e.g., Highest, High, Medium, Low, Lowest). Returns IDs, names, and descriptions."};
2419
2365
  async function handleGetPriorities(input) {
2420
2366
  try {
2421
2367
  validateInput(GetPrioritiesInputSchema, input);
@@ -2432,23 +2378,7 @@ async function handleGetPriorities(input) {
2432
2378
  // src/tools/get-statuses.ts
2433
2379
  var log10 = createLogger("tool:get-statuses");
2434
2380
  var getStatusesTool = {
2435
- name: TOOL_NAMES.GET_STATUSES,
2436
- description: "Retrieves available statuses (global or project-specific, e.g., To Do, In Progress, Done). Returns status categories and workflow information.",
2437
- inputSchema: {
2438
- type: "object",
2439
- properties: {
2440
- projectKey: {
2441
- type: "string",
2442
- description: "Project key to get statuses for specific project (optional)"
2443
- },
2444
- issueTypeId: {
2445
- type: "string",
2446
- description: "Issue type ID to get statuses for specific issue type (requires projectKey)"
2447
- }
2448
- },
2449
- required: []
2450
- }
2451
- };
2381
+ description: "Retrieves available statuses (global or project-specific, e.g., To Do, In Progress, Done). Returns status categories and workflow information."};
2452
2382
  async function handleGetStatuses(input) {
2453
2383
  try {
2454
2384
  const validated = validateInput(GetStatusesInputSchema, input);
@@ -2476,68 +2406,7 @@ async function handleGetStatuses(input) {
2476
2406
  // src/tools/create-issue.ts
2477
2407
  var log11 = createLogger("tool:create-issue");
2478
2408
  var createIssueTool = {
2479
- name: TOOL_NAMES.CREATE_ISSUE,
2480
- 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.',
2481
- inputSchema: {
2482
- type: "object",
2483
- properties: {
2484
- projectKey: {
2485
- type: "string",
2486
- description: "Project key where the issue will be created"
2487
- },
2488
- summary: {
2489
- type: "string",
2490
- description: "Issue summary/title",
2491
- minLength: 1
2492
- },
2493
- description: {
2494
- anyOf: [{ type: "string" }, { type: "object" }],
2495
- description: 'Detailed issue description (optional). Format depends on the "format" parameter.'
2496
- },
2497
- issueType: {
2498
- type: "string",
2499
- description: "Issue type name (e.g., Bug, Story, Task, Epic)"
2500
- },
2501
- priority: {
2502
- type: "string",
2503
- description: "Issue priority name (e.g., High, Medium, Low) - optional"
2504
- },
2505
- assignee: {
2506
- type: "string",
2507
- description: "Assignee account ID (optional)"
2508
- },
2509
- labels: {
2510
- type: "array",
2511
- items: { type: "string" },
2512
- description: "Issue labels (optional)",
2513
- default: []
2514
- },
2515
- components: {
2516
- type: "array",
2517
- items: { type: "string" },
2518
- description: "Component names (optional)",
2519
- default: []
2520
- },
2521
- customFields: {
2522
- type: "object",
2523
- additionalProperties: true,
2524
- description: 'Additional Jira fields, e.g. { "customfield_10071": value }. Use this to set required custom fields.'
2525
- },
2526
- returnIssue: {
2527
- type: "boolean",
2528
- description: "If false, returns only the issue key without fetching full details",
2529
- default: true
2530
- },
2531
- format: {
2532
- type: "string",
2533
- enum: ["markdown", "adf", "plain"],
2534
- description: 'Description format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2535
- default: "markdown"
2536
- }
2537
- },
2538
- required: ["projectKey", "summary", "issueType"]
2539
- }
2540
- };
2409
+ 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.'};
2541
2410
  async function handleCreateIssue(input) {
2542
2411
  try {
2543
2412
  const validated = validateInput(CreateIssueInputSchema, input);
@@ -2577,56 +2446,7 @@ async function handleCreateIssue(input) {
2577
2446
  // src/tools/update-issue.ts
2578
2447
  var log12 = createLogger("tool:update-issue");
2579
2448
  var updateIssueTool = {
2580
- name: TOOL_NAMES.UPDATE_ISSUE,
2581
- 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.',
2582
- inputSchema: {
2583
- type: "object",
2584
- properties: {
2585
- issueKey: {
2586
- type: "string",
2587
- description: "Issue key to update (e.g., PROJECT-123)"
2588
- },
2589
- summary: {
2590
- type: "string",
2591
- description: "New issue summary/title (optional)"
2592
- },
2593
- description: {
2594
- anyOf: [{ type: "string" }, { type: "object" }],
2595
- description: 'New issue description (optional). Format depends on the "format" parameter.'
2596
- },
2597
- priority: {
2598
- type: "string",
2599
- description: "New priority name (e.g., High, Medium, Low) - optional"
2600
- },
2601
- assignee: {
2602
- type: "string",
2603
- description: "New assignee account ID (optional, use null string to unassign)"
2604
- },
2605
- labels: {
2606
- type: "array",
2607
- items: { type: "string" },
2608
- description: "New labels array (replaces existing labels) - optional"
2609
- },
2610
- components: {
2611
- type: "array",
2612
- items: { type: "string" },
2613
- description: "New components array (replaces existing components) - optional"
2614
- },
2615
- returnIssue: {
2616
- type: "boolean",
2617
- description: "If false, returns a success message without fetching the updated issue",
2618
- default: true
2619
- },
2620
- format: {
2621
- type: "string",
2622
- enum: ["markdown", "adf", "plain"],
2623
- description: 'Description format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2624
- default: "markdown"
2625
- }
2626
- },
2627
- required: ["issueKey"]
2628
- }
2629
- };
2449
+ 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.'};
2630
2450
  async function handleUpdateIssue(input) {
2631
2451
  try {
2632
2452
  const validated = validateInput(UpdateIssueInputSchema, input);
@@ -2638,6 +2458,7 @@ async function handleUpdateIssue(input) {
2638
2458
  if (validated.assignee !== void 0) updateParams.assignee = validated.assignee;
2639
2459
  if (validated.labels !== void 0) updateParams.labels = validated.labels;
2640
2460
  if (validated.components !== void 0) updateParams.components = validated.components;
2461
+ if (validated.parent !== void 0) updateParams.parent = validated.parent;
2641
2462
  if (validated.format !== void 0) updateParams.format = validated.format;
2642
2463
  await updateIssue(validated.issueKey, updateParams);
2643
2464
  if (validated.returnIssue === false) {
@@ -2656,46 +2477,7 @@ async function handleUpdateIssue(input) {
2656
2477
  // src/tools/add-comment.ts
2657
2478
  var log13 = createLogger("tool:add-comment");
2658
2479
  var addCommentTool = {
2659
- name: TOOL_NAMES.ADD_COMMENT,
2660
- 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.',
2661
- inputSchema: {
2662
- type: "object",
2663
- properties: {
2664
- issueKey: {
2665
- type: "string",
2666
- description: "Issue key to add comment to (e.g., PROJECT-123)"
2667
- },
2668
- body: {
2669
- type: "string",
2670
- description: 'Comment body text. Format depends on the "format" parameter.',
2671
- minLength: 1
2672
- },
2673
- visibility: {
2674
- type: "object",
2675
- description: "Comment visibility restrictions (optional)",
2676
- properties: {
2677
- type: {
2678
- type: "string",
2679
- enum: ["group", "role"],
2680
- description: 'Visibility type - either "group" or "role"'
2681
- },
2682
- value: {
2683
- type: "string",
2684
- description: "Group name or role name for visibility restriction"
2685
- }
2686
- },
2687
- required: ["type", "value"]
2688
- },
2689
- format: {
2690
- type: "string",
2691
- enum: ["markdown", "adf", "plain"],
2692
- description: 'Comment format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2693
- default: "markdown"
2694
- }
2695
- },
2696
- required: ["issueKey", "body"]
2697
- }
2698
- };
2480
+ 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.'};
2699
2481
  async function handleAddComment(input) {
2700
2482
  try {
2701
2483
  const validated = validateInput(AddCommentInputSchema, input);
@@ -2717,25 +2499,7 @@ async function handleAddComment(input) {
2717
2499
  // src/tools/get-project-info.ts
2718
2500
  var log14 = createLogger("tool:get-project-info");
2719
2501
  var getProjectInfoTool = {
2720
- name: TOOL_NAMES.GET_PROJECT_INFO,
2721
- description: "Retrieves detailed information about a project (components, versions, issue types, roles, insights). More comprehensive than the basic project list.",
2722
- inputSchema: {
2723
- type: "object",
2724
- properties: {
2725
- projectKey: {
2726
- type: "string",
2727
- description: "Project key to get detailed information for"
2728
- },
2729
- expand: {
2730
- type: "array",
2731
- items: { type: "string" },
2732
- description: 'Additional project details to include (e.g., ["description", "lead", "issueTypes", "versions"])',
2733
- default: []
2734
- }
2735
- },
2736
- required: ["projectKey"]
2737
- }
2738
- };
2502
+ description: "Retrieves detailed information about a project (components, versions, issue types, roles, insights). More comprehensive than the basic project list."};
2739
2503
  async function handleGetProjectInfo(input) {
2740
2504
  try {
2741
2505
  const validated = validateInput(GetProjectInfoInputSchema, input);
@@ -2752,54 +2516,7 @@ async function handleGetProjectInfo(input) {
2752
2516
  // src/tools/create-subtask.ts
2753
2517
  var log15 = createLogger("tool:create-subtask");
2754
2518
  var createSubtaskTool = {
2755
- name: TOOL_NAMES.CREATE_SUBTASK,
2756
- 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).',
2757
- inputSchema: {
2758
- type: "object",
2759
- properties: {
2760
- parentIssueKey: {
2761
- type: "string",
2762
- description: "Parent issue key (e.g., PROJECT-123)"
2763
- },
2764
- summary: {
2765
- type: "string",
2766
- description: "Subtask summary/title",
2767
- minLength: 1
2768
- },
2769
- description: {
2770
- type: "string",
2771
- description: 'Detailed subtask description (optional). Format depends on the "format" parameter.'
2772
- },
2773
- priority: {
2774
- type: "string",
2775
- description: "Subtask priority name (e.g., High, Medium, Low) - optional"
2776
- },
2777
- assignee: {
2778
- type: "string",
2779
- description: "Assignee account ID (optional)"
2780
- },
2781
- labels: {
2782
- type: "array",
2783
- items: { type: "string" },
2784
- description: "Subtask labels (optional)",
2785
- default: []
2786
- },
2787
- components: {
2788
- type: "array",
2789
- items: { type: "string" },
2790
- description: "Component names (optional)",
2791
- default: []
2792
- },
2793
- format: {
2794
- type: "string",
2795
- enum: ["markdown", "adf", "plain"],
2796
- description: 'Description format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2797
- default: "markdown"
2798
- }
2799
- },
2800
- required: ["parentIssueKey", "summary"]
2801
- }
2802
- };
2519
+ 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).'};
2803
2520
  async function handleCreateSubtask(input) {
2804
2521
  try {
2805
2522
  const validated = validateInput(CreateSubtaskInputSchema, input);
@@ -2825,23 +2542,7 @@ async function handleCreateSubtask(input) {
2825
2542
  // src/tools/get-create-meta.ts
2826
2543
  var log16 = createLogger("tool:get-create-meta");
2827
2544
  var getCreateMetaTool = {
2828
- name: TOOL_NAMES.GET_CREATE_META,
2829
- description: "Retrieves create metadata for a project, showing all available fields (including custom fields) for creating issues. Shows required vs optional fields, field types, and allowed values. Use this before creating issues to discover what fields are needed.",
2830
- inputSchema: {
2831
- type: "object",
2832
- properties: {
2833
- projectKey: {
2834
- type: "string",
2835
- description: "Project key to get create metadata for (e.g., PROJECT)"
2836
- },
2837
- issueTypeName: {
2838
- type: "string",
2839
- description: "Specific issue type name to get metadata for (optional)"
2840
- }
2841
- },
2842
- required: ["projectKey"]
2843
- }
2844
- };
2545
+ description: "Retrieves create metadata for a project, showing all available fields (including custom fields) for creating issues. Shows required vs optional fields, field types, and allowed values. Use this before creating issues to discover what fields are needed."};
2845
2546
  async function handleGetCreateMeta(input) {
2846
2547
  try {
2847
2548
  const validated = validateInput(GetCreateMetaInputSchema, input);
@@ -2865,19 +2566,7 @@ async function handleGetCreateMeta(input) {
2865
2566
  // src/tools/get-custom-fields.ts
2866
2567
  var log17 = createLogger("tool:get-custom-fields");
2867
2568
  var getCustomFieldsTool = {
2868
- name: TOOL_NAMES.GET_CUSTOM_FIELDS,
2869
- description: "Retrieves all custom fields available in Jira. Shows custom field names, IDs (e.g., customfield_10071), and types. Useful for discovering what custom fields exist and their identifiers.",
2870
- inputSchema: {
2871
- type: "object",
2872
- properties: {
2873
- projectKey: {
2874
- type: "string",
2875
- description: "Project key to filter custom fields (optional)"
2876
- }
2877
- },
2878
- required: []
2879
- }
2880
- };
2569
+ description: "Retrieves all custom fields available in Jira. Shows custom field names, IDs (e.g., customfield_10071), and types. Useful for discovering what custom fields exist and their identifiers."};
2881
2570
  async function handleGetCustomFields(input) {
2882
2571
  try {
2883
2572
  const validated = validateInput(GetCustomFieldsInputSchema, input);
@@ -2895,27 +2584,7 @@ async function handleGetCustomFields(input) {
2895
2584
  // src/tools/create-issue-link.ts
2896
2585
  var log18 = createLogger("tool:create-issue-link");
2897
2586
  var createIssueLinkTool = {
2898
- name: TOOL_NAMES.CREATE_ISSUE_LINK,
2899
- description: 'Creates a link between two Jira issues. Supports common link types like "blocks", "relates", "duplicates", and "clones". Use this to establish relationships between issues.',
2900
- inputSchema: {
2901
- type: "object",
2902
- properties: {
2903
- fromIssue: {
2904
- type: "string",
2905
- description: "Source issue key (e.g., PROJECT-123)"
2906
- },
2907
- toIssue: {
2908
- type: "string",
2909
- description: "Target issue key (e.g., PROJECT-456)"
2910
- },
2911
- linkType: {
2912
- type: "string",
2913
- description: 'Link type: "blocks" (fromIssue blocks toIssue), "is blocked by", "relates", "duplicates", "clones", or custom link type name'
2914
- }
2915
- },
2916
- required: ["fromIssue", "toIssue", "linkType"]
2917
- }
2918
- };
2587
+ description: 'Creates a link between two Jira issues. Supports common link types like "blocks", "relates", "duplicates", and "clones". Use this to establish relationships between issues.'};
2919
2588
  async function handleCreateIssueLink(input) {
2920
2589
  try {
2921
2590
  const validated = validateInput(CreateIssueLinkInputSchema, input);
@@ -2936,35 +2605,7 @@ async function handleCreateIssueLink(input) {
2936
2605
  // src/tools/get-comments.ts
2937
2606
  var log19 = createLogger("tool:get-comments");
2938
2607
  var getCommentsTool = {
2939
- name: TOOL_NAMES.GET_COMMENTS,
2940
- description: "Retrieves all comments for a Jira issue. Returns comment author, content, timestamps, and visibility settings. Supports pagination for issues with many comments.",
2941
- inputSchema: {
2942
- type: "object",
2943
- properties: {
2944
- issueKey: {
2945
- type: "string",
2946
- description: "Issue key to get comments for (e.g., PROJECT-123)"
2947
- },
2948
- maxResults: {
2949
- type: "number",
2950
- description: "Maximum number of comments to return (default: 50, max: 100)",
2951
- minimum: 1,
2952
- maximum: 100
2953
- },
2954
- orderBy: {
2955
- type: "string",
2956
- enum: ["created", "-created", "+created"],
2957
- description: 'Sort order for comments: "created" or "+created" (oldest first), "-created" (newest first)'
2958
- },
2959
- startAt: {
2960
- type: "number",
2961
- description: "Index of first comment to return (for pagination, default: 0)",
2962
- minimum: 0
2963
- }
2964
- },
2965
- required: ["issueKey"]
2966
- }
2967
- };
2608
+ description: "Retrieves all comments for a Jira issue. Returns comment author, content, timestamps, and visibility settings. Supports pagination for issues with many comments."};
2968
2609
  async function handleGetComments(input) {
2969
2610
  try {
2970
2611
  const validated = validateInput(GetCommentsInputSchema, input);
@@ -2991,7 +2632,6 @@ async function handleGetComments(input) {
2991
2632
  // src/tools/add-attachment.ts
2992
2633
  var log20 = createLogger("tool:add-attachment");
2993
2634
  var addAttachmentTool = {
2994
- name: TOOL_NAMES.ADD_ATTACHMENT,
2995
2635
  description: `Uploads an attachment (image, document, etc.) to a Jira issue.
2996
2636
 
2997
2637
  **EFFICIENT METHOD (recommended for large files):**
@@ -3002,37 +2642,7 @@ var addAttachmentTool = {
3002
2642
  - content: Base64 encoded content - WARNING: HIGH token cost (~330,000 tokens for 1MB file)
3003
2643
  Only suitable for small files (< 500 KB)
3004
2644
 
3005
- Returns attachment metadata including ID and download URL. To reference the image in a comment or description, use wiki markup: !filename.png! or !filename.png|thumbnail!`,
3006
- inputSchema: {
3007
- type: "object",
3008
- properties: {
3009
- issueKey: {
3010
- type: "string",
3011
- description: "Issue key to add attachment to (e.g., PROJECT-123)"
3012
- },
3013
- filename: {
3014
- type: "string",
3015
- description: "Name of the file to attach (e.g., screenshot.png, document.pdf)",
3016
- minLength: 1
3017
- },
3018
- fileUrl: {
3019
- type: "string",
3020
- description: "URL to download file from (efficient for remote files, ~60 tokens). Upload large files to Dropbox/S3/imgur first, then use URL.",
3021
- format: "uri"
3022
- },
3023
- content: {
3024
- type: "string",
3025
- description: "Base64-encoded or plain text content. WARNING: HIGH token cost (~330k tokens for 1MB file). Use fileUrl for large files or upload to cloud storage first."
3026
- },
3027
- isBase64: {
3028
- type: "boolean",
3029
- description: "Whether content is base64-encoded (default: true). Set to false for plain text files.",
3030
- default: true
3031
- }
3032
- },
3033
- required: ["issueKey", "filename"]
3034
- }
3035
- };
2645
+ Returns attachment metadata including ID and download URL. To reference the image in a comment or description, use wiki markup: !filename.png! or !filename.png|thumbnail!`};
3036
2646
  async function handleAddAttachment(input) {
3037
2647
  try {
3038
2648
  const validated = validateInput(AddAttachmentInputSchema, input);
@@ -3067,19 +2677,7 @@ async function handleAddAttachment(input) {
3067
2677
  // src/tools/get-attachments.ts
3068
2678
  var log21 = createLogger("tool:get-attachments");
3069
2679
  var getAttachmentsTool = {
3070
- name: TOOL_NAMES.GET_ATTACHMENTS,
3071
- description: "Lists all attachments on a Jira issue. Returns attachment metadata including filename, size, MIME type, author, and download URL.",
3072
- inputSchema: {
3073
- type: "object",
3074
- properties: {
3075
- issueKey: {
3076
- type: "string",
3077
- description: "Issue key to get attachments for (e.g., PROJECT-123)"
3078
- }
3079
- },
3080
- required: ["issueKey"]
3081
- }
3082
- };
2680
+ description: "Lists all attachments on a Jira issue. Returns attachment metadata including filename, size, MIME type, author, and download URL."};
3083
2681
  async function handleGetAttachments(input) {
3084
2682
  try {
3085
2683
  const validated = validateInput(GetAttachmentsInputSchema, input);
@@ -3096,20 +2694,7 @@ async function handleGetAttachments(input) {
3096
2694
  // src/tools/delete-attachment.ts
3097
2695
  var log22 = createLogger("tool:delete-attachment");
3098
2696
  var deleteAttachmentTool = {
3099
- name: TOOL_NAMES.DELETE_ATTACHMENT,
3100
- description: "Deletes an attachment from Jira by its attachment ID. Use jira_get_attachments to find attachment IDs.",
3101
- inputSchema: {
3102
- type: "object",
3103
- properties: {
3104
- attachmentId: {
3105
- type: "string",
3106
- description: "ID of the attachment to delete (can be found using jira_get_attachments)",
3107
- minLength: 1
3108
- }
3109
- },
3110
- required: ["attachmentId"]
3111
- }
3112
- };
2697
+ description: "Deletes an attachment from Jira by its attachment ID. Use jira_get_attachments to find attachment IDs."};
3113
2698
  async function handleDeleteAttachment(input) {
3114
2699
  try {
3115
2700
  const validated = validateInput(DeleteAttachmentInputSchema, input);
@@ -3130,6 +2715,322 @@ async function handleDeleteAttachment(input) {
3130
2715
  }
3131
2716
  }
3132
2717
 
2718
+ // src/tools/get-transitions.ts
2719
+ var log23 = createLogger("tool:get-transitions");
2720
+ var getTransitionsTool = {
2721
+ description: "Retrieves all available workflow transitions for a Jira issue. Use this to discover which status changes are possible for an issue before calling jira_transition_issue."};
2722
+ async function handleGetTransitions(input) {
2723
+ try {
2724
+ const validated = validateInput(GetTransitionsInputSchema, input);
2725
+ log23.info(`Getting transitions for issue ${validated.issueKey}...`);
2726
+ const transitions = await getTransitions(validated.issueKey);
2727
+ log23.info(`Found ${transitions.length} transition(s) for ${validated.issueKey}`);
2728
+ return formatTransitionsResponse(transitions, validated.issueKey);
2729
+ } catch (error) {
2730
+ log23.error("Error in handleGetTransitions:", error);
2731
+ return handleError(error);
2732
+ }
2733
+ }
2734
+
2735
+ // src/tools/transition-issue.ts
2736
+ var log24 = createLogger("tool:transition-issue");
2737
+ var transitionIssueTool = {
2738
+ description: 'Transitions a Jira issue to a new workflow status (e.g., "To Do" -> "In Progress" -> "Done"). Use jira_get_transitions first to discover available transitions. Supports adding a comment and setting resolution during the transition.'};
2739
+ async function handleTransitionIssue(input) {
2740
+ try {
2741
+ const validated = validateInput(TransitionIssueInputSchema, input);
2742
+ let transitionId = validated.transitionId;
2743
+ if (!transitionId && validated.transitionName) {
2744
+ log24.info(
2745
+ `Looking up transition "${validated.transitionName}" for issue ${validated.issueKey}...`
2746
+ );
2747
+ const transitions = await getTransitions(validated.issueKey);
2748
+ const match = transitions.find(
2749
+ (t) => t.name.toLowerCase() === validated.transitionName.toLowerCase()
2750
+ );
2751
+ if (!match) {
2752
+ const available = transitions.map((t) => `"${t.name}" (ID: ${t.id})`).join(", ");
2753
+ return {
2754
+ content: [
2755
+ {
2756
+ type: "text",
2757
+ text: `Transition "${validated.transitionName}" not found for issue ${validated.issueKey}.
2758
+
2759
+ Available transitions: ${available || "none"}`
2760
+ }
2761
+ ]
2762
+ };
2763
+ }
2764
+ transitionId = match.id;
2765
+ }
2766
+ log24.info(`Transitioning issue ${validated.issueKey} with transition ${transitionId}...`);
2767
+ const transitionOptions = {};
2768
+ if (validated.comment) transitionOptions.comment = validated.comment;
2769
+ if (validated.resolution) transitionOptions.resolution = validated.resolution;
2770
+ if (validated.format) transitionOptions.format = validated.format;
2771
+ await transitionIssue(validated.issueKey, transitionId, transitionOptions);
2772
+ const updatedIssue = await getIssue(validated.issueKey);
2773
+ log24.info(`Transitioned issue ${validated.issueKey} to ${updatedIssue.fields.status.name}`);
2774
+ return formatIssueResponse(updatedIssue);
2775
+ } catch (error) {
2776
+ log24.error("Error in handleTransitionIssue:", error);
2777
+ return handleError(error);
2778
+ }
2779
+ }
2780
+
2781
+ // src/tools/get-issue-graph.ts
2782
+ var log25 = createLogger("tool:get-issue-graph");
2783
+ var GRAPH_FIELDS = [
2784
+ "summary",
2785
+ "status",
2786
+ "issuetype",
2787
+ "priority",
2788
+ "parent",
2789
+ "subtasks",
2790
+ "issuelinks"
2791
+ ];
2792
+ var getIssueGraphTool = {
2793
+ description: "Build a dependency/relationship graph starting from a seed issue. Returns a map of connected issues (parent/child hierarchy, blocks, relates to, duplicates, etc.) with nodes and edges, plus a Mermaid diagram for visualization. Use this to understand how issues are connected across epics, stories, and subtasks."};
2794
+ function extractIssueData(issue) {
2795
+ const fields = issue.fields;
2796
+ return {
2797
+ key: issue.key,
2798
+ summary: fields?.summary || "No summary",
2799
+ status: fields?.status?.name || "Unknown",
2800
+ statusCategory: fields?.status?.statusCategory?.name || "Unknown",
2801
+ issueType: fields?.issuetype?.name || "Unknown",
2802
+ priority: fields?.priority?.name || "None",
2803
+ issuelinks: fields?.issuelinks || [],
2804
+ parent: fields?.parent ? {
2805
+ key: fields.parent.key,
2806
+ summary: fields.parent.fields?.summary,
2807
+ status: fields.parent.fields?.status?.name,
2808
+ issueType: fields.parent.fields?.issuetype?.name
2809
+ } : void 0,
2810
+ subtasks: fields?.subtasks || []
2811
+ };
2812
+ }
2813
+ function buildGraph(issueDataMap, rootKey, options) {
2814
+ const nodes = [];
2815
+ const edges = [];
2816
+ const edgeSet = /* @__PURE__ */ new Set();
2817
+ const addEdge = (edge) => {
2818
+ const edgeId = `${edge.from}->${edge.to}:${edge.type}:${edge.category}`;
2819
+ if (!edgeSet.has(edgeId)) {
2820
+ edgeSet.add(edgeId);
2821
+ edges.push(edge);
2822
+ }
2823
+ };
2824
+ for (const [, data] of issueDataMap) {
2825
+ nodes.push({
2826
+ key: data.key,
2827
+ summary: data.summary,
2828
+ status: data.status,
2829
+ statusCategory: data.statusCategory,
2830
+ issueType: data.issueType,
2831
+ priority: data.priority
2832
+ });
2833
+ if (options.includeHierarchy) {
2834
+ if (data.parent && issueDataMap.has(data.parent.key)) {
2835
+ addEdge({
2836
+ from: data.parent.key,
2837
+ to: data.key,
2838
+ type: "parent of",
2839
+ label: "parent of",
2840
+ category: "hierarchy"
2841
+ });
2842
+ }
2843
+ for (const sub of data.subtasks) {
2844
+ if (issueDataMap.has(sub.key)) {
2845
+ addEdge({
2846
+ from: data.key,
2847
+ to: sub.key,
2848
+ type: "parent of",
2849
+ label: "parent of",
2850
+ category: "hierarchy"
2851
+ });
2852
+ }
2853
+ }
2854
+ }
2855
+ for (const link of data.issuelinks) {
2856
+ const typeName = link.type?.name || "Unknown";
2857
+ if (options.linkTypes && options.linkTypes.length > 0) {
2858
+ if (!options.linkTypes.some((lt) => lt.toLowerCase() === typeName.toLowerCase())) {
2859
+ continue;
2860
+ }
2861
+ }
2862
+ if (link.outwardIssue && (options.direction === "all" || options.direction === "outward")) {
2863
+ if (issueDataMap.has(link.outwardIssue.key)) {
2864
+ addEdge({
2865
+ from: data.key,
2866
+ to: link.outwardIssue.key,
2867
+ type: typeName,
2868
+ label: link.type?.outward || typeName,
2869
+ category: "link"
2870
+ });
2871
+ }
2872
+ }
2873
+ if (link.inwardIssue && (options.direction === "all" || options.direction === "inward")) {
2874
+ if (issueDataMap.has(link.inwardIssue.key)) {
2875
+ addEdge({
2876
+ from: link.inwardIssue.key,
2877
+ to: data.key,
2878
+ type: typeName,
2879
+ label: link.type?.outward || typeName,
2880
+ category: "link"
2881
+ });
2882
+ }
2883
+ }
2884
+ }
2885
+ }
2886
+ return {
2887
+ rootIssue: rootKey,
2888
+ nodes,
2889
+ edges,
2890
+ metadata: {
2891
+ totalNodes: nodes.length,
2892
+ totalEdges: edges.length,
2893
+ maxDepthReached: false,
2894
+ // set by caller
2895
+ traversalDepth: 0
2896
+ // set by caller
2897
+ }
2898
+ };
2899
+ }
2900
+ function formatGraphResponse(graph) {
2901
+ const { nodes, edges, metadata, rootIssue } = graph;
2902
+ const nodesTable = nodes.map((n) => `| ${n.key} | ${n.summary} | ${n.status} | ${n.issueType} | ${n.priority} |`).join("\n");
2903
+ const edgesTable = edges.map((e) => `| ${e.from} | ${e.label} | ${e.to} | ${e.category} |`).join("\n");
2904
+ const mermaidNodes = nodes.map((n) => {
2905
+ const shape = n.statusCategory === "Done" ? `${n.key}[/"${n.issueType}: ${escapeMermaid(n.summary)}"/]` : n.statusCategory === "In Progress" ? `${n.key}[["${n.issueType}: ${escapeMermaid(n.summary)}"]]` : `${n.key}["${n.issueType}: ${escapeMermaid(n.summary)}"]`;
2906
+ return ` ${shape}`;
2907
+ }).join("\n");
2908
+ const mermaidEdges = edges.map((e) => {
2909
+ if (e.category === "hierarchy") {
2910
+ return ` ${e.from} --> ${e.to}`;
2911
+ }
2912
+ return ` ${e.from} -->|${escapeMermaid(e.label)}| ${e.to}`;
2913
+ }).join("\n");
2914
+ const doneNodes = nodes.filter((n) => n.statusCategory === "Done").map((n) => n.key);
2915
+ const inProgressNodes = nodes.filter((n) => n.statusCategory === "In Progress").map((n) => n.key);
2916
+ const todoNodes = nodes.filter((n) => n.statusCategory !== "Done" && n.statusCategory !== "In Progress").map((n) => n.key);
2917
+ let mermaidStyles = "";
2918
+ if (doneNodes.length > 0) mermaidStyles += `
2919
+ classDef done fill:#d4edda,stroke:#28a745`;
2920
+ if (inProgressNodes.length > 0)
2921
+ mermaidStyles += `
2922
+ classDef inprogress fill:#fff3cd,stroke:#ffc107`;
2923
+ if (todoNodes.length > 0) mermaidStyles += `
2924
+ classDef todo fill:#f8d7da,stroke:#dc3545`;
2925
+ if (doneNodes.length > 0) mermaidStyles += `
2926
+ class ${doneNodes.join(",")} done`;
2927
+ if (inProgressNodes.length > 0)
2928
+ mermaidStyles += `
2929
+ class ${inProgressNodes.join(",")} inprogress`;
2930
+ if (todoNodes.length > 0) mermaidStyles += `
2931
+ class ${todoNodes.join(",")} todo`;
2932
+ const depthNote = metadata.maxDepthReached ? " (max depth reached, graph may be incomplete)" : "";
2933
+ const text = `## Issue Graph: ${rootIssue} (depth: ${metadata.traversalDepth}, ${metadata.totalNodes} nodes, ${metadata.totalEdges} edges)${depthNote}
2934
+
2935
+ ### Nodes
2936
+ | Key | Summary | Status | Type | Priority |
2937
+ |-----|---------|--------|------|----------|
2938
+ ${nodesTable}
2939
+
2940
+ ### Edges
2941
+ | From | Relationship | To | Category |
2942
+ |------|-------------|-----|----------|
2943
+ ${edgesTable}
2944
+
2945
+ ### Mermaid Diagram
2946
+ \`\`\`mermaid
2947
+ graph TD
2948
+ ${mermaidNodes}
2949
+ ${mermaidEdges}${mermaidStyles}
2950
+ \`\`\``;
2951
+ return { content: [{ type: "text", text }] };
2952
+ }
2953
+ function escapeMermaid(text) {
2954
+ return text.replace(/"/g, "'").replace(/[[\]{}()#]/g, "").substring(0, 60);
2955
+ }
2956
+ async function handleGetIssueGraph(input) {
2957
+ try {
2958
+ const validated = validateInput(GetIssueGraphInputSchema, input);
2959
+ const {
2960
+ issueKey,
2961
+ maxDepth = 2,
2962
+ maxNodes = 50,
2963
+ includeHierarchy = true,
2964
+ linkTypes,
2965
+ direction = "all"
2966
+ } = validated;
2967
+ log25.info(`Building issue graph from ${issueKey} (depth: ${maxDepth}, maxNodes: ${maxNodes})`);
2968
+ const issueDataMap = /* @__PURE__ */ new Map();
2969
+ const queue = [{ key: issueKey, depth: 0 }];
2970
+ const visited = /* @__PURE__ */ new Set();
2971
+ let maxDepthReached = false;
2972
+ let actualDepth = 0;
2973
+ while (queue.length > 0 && issueDataMap.size < maxNodes) {
2974
+ const current = queue.shift();
2975
+ if (visited.has(current.key)) continue;
2976
+ visited.add(current.key);
2977
+ if (current.depth > maxDepth) {
2978
+ maxDepthReached = true;
2979
+ continue;
2980
+ }
2981
+ actualDepth = Math.max(actualDepth, current.depth);
2982
+ let issue;
2983
+ try {
2984
+ issue = await getIssue(current.key, { fields: GRAPH_FIELDS });
2985
+ } catch (err) {
2986
+ log25.warn(`Skipping issue ${current.key}: ${err.message}`);
2987
+ continue;
2988
+ }
2989
+ const data = extractIssueData(issue);
2990
+ issueDataMap.set(current.key, data);
2991
+ if (current.depth >= maxDepth) continue;
2992
+ const nextDepth = current.depth + 1;
2993
+ if (includeHierarchy && data.parent && !visited.has(data.parent.key)) {
2994
+ queue.push({ key: data.parent.key, depth: nextDepth });
2995
+ }
2996
+ if (includeHierarchy) {
2997
+ for (const sub of data.subtasks) {
2998
+ if (!visited.has(sub.key)) {
2999
+ queue.push({ key: sub.key, depth: nextDepth });
3000
+ }
3001
+ }
3002
+ }
3003
+ for (const link of data.issuelinks) {
3004
+ if (linkTypes && linkTypes.length > 0) {
3005
+ const typeName = link.type?.name || "";
3006
+ if (!linkTypes.some((lt) => lt.toLowerCase() === typeName.toLowerCase())) continue;
3007
+ }
3008
+ if (link.outwardIssue && !visited.has(link.outwardIssue.key) && (direction === "all" || direction === "outward")) {
3009
+ queue.push({ key: link.outwardIssue.key, depth: nextDepth });
3010
+ }
3011
+ if (link.inwardIssue && !visited.has(link.inwardIssue.key) && (direction === "all" || direction === "inward")) {
3012
+ queue.push({ key: link.inwardIssue.key, depth: nextDepth });
3013
+ }
3014
+ }
3015
+ }
3016
+ if (queue.length > 0) {
3017
+ maxDepthReached = true;
3018
+ }
3019
+ log25.info(`Graph built: ${issueDataMap.size} nodes discovered`);
3020
+ const graph = buildGraph(issueDataMap, issueKey, {
3021
+ includeHierarchy,
3022
+ linkTypes,
3023
+ direction
3024
+ });
3025
+ graph.metadata.maxDepthReached = maxDepthReached;
3026
+ graph.metadata.traversalDepth = actualDepth;
3027
+ return formatGraphResponse(graph);
3028
+ } catch (error) {
3029
+ log25.error("Error in handleGetIssueGraph:", error);
3030
+ return handleError(error);
3031
+ }
3032
+ }
3033
+
3133
3034
  // src/index.ts
3134
3035
  if (process.env.NODE_ENV !== "production") {
3135
3036
  try {
@@ -3138,50 +3039,172 @@ if (process.env.NODE_ENV !== "production") {
3138
3039
  } catch {
3139
3040
  }
3140
3041
  }
3141
- var log23 = createLogger("server");
3142
- var toolHandlers = /* @__PURE__ */ new Map([
3143
- [TOOL_NAMES.GET_VISIBLE_PROJECTS, handleGetVisibleProjects],
3144
- [TOOL_NAMES.GET_ISSUE, handleGetIssue],
3145
- [TOOL_NAMES.SEARCH_ISSUES, handleSearchIssues],
3146
- [TOOL_NAMES.GET_MY_ISSUES, handleGetMyIssues],
3147
- [TOOL_NAMES.GET_ISSUE_TYPES, handleGetIssueTypes],
3148
- [TOOL_NAMES.GET_USERS, handleGetUsers],
3149
- [TOOL_NAMES.GET_PRIORITIES, handleGetPriorities],
3150
- [TOOL_NAMES.GET_STATUSES, handleGetStatuses],
3151
- [TOOL_NAMES.CREATE_ISSUE, handleCreateIssue],
3152
- [TOOL_NAMES.UPDATE_ISSUE, handleUpdateIssue],
3153
- [TOOL_NAMES.ADD_COMMENT, handleAddComment],
3154
- [TOOL_NAMES.GET_PROJECT_INFO, handleGetProjectInfo],
3155
- [TOOL_NAMES.CREATE_SUBTASK, handleCreateSubtask],
3156
- [TOOL_NAMES.GET_CREATE_META, handleGetCreateMeta],
3157
- [TOOL_NAMES.GET_CUSTOM_FIELDS, handleGetCustomFields],
3158
- [TOOL_NAMES.CREATE_ISSUE_LINK, handleCreateIssueLink],
3159
- [TOOL_NAMES.GET_COMMENTS, handleGetComments],
3160
- [TOOL_NAMES.ADD_ATTACHMENT, handleAddAttachment],
3161
- [TOOL_NAMES.GET_ATTACHMENTS, handleGetAttachments],
3162
- [TOOL_NAMES.DELETE_ATTACHMENT, handleDeleteAttachment]
3163
- ]);
3164
- var allTools = [
3165
- getVisibleProjectsTool,
3166
- getIssueTool,
3167
- searchIssuesTool,
3168
- getMyIssuesTool,
3169
- getIssueTypesTool,
3170
- getUsersTool,
3171
- getPrioritiesTool,
3172
- getStatusesTool,
3173
- createIssueTool,
3174
- updateIssueTool,
3175
- addCommentTool,
3176
- getProjectInfoTool,
3177
- createSubtaskTool,
3178
- getCreateMetaTool,
3179
- getCustomFieldsTool,
3180
- createIssueLinkTool,
3181
- getCommentsTool,
3182
- addAttachmentTool,
3183
- getAttachmentsTool,
3184
- deleteAttachmentTool
3042
+ var log26 = createLogger("server");
3043
+ var toolRegistry = [
3044
+ // === Read-only tools ===
3045
+ {
3046
+ name: TOOL_NAMES.GET_VISIBLE_PROJECTS,
3047
+ description: getVisibleProjectsTool.description,
3048
+ inputSchema: GetVisibleProjectsInputSchema,
3049
+ handler: handleGetVisibleProjects,
3050
+ annotations: { readOnlyHint: true }
3051
+ },
3052
+ {
3053
+ name: TOOL_NAMES.GET_ISSUE,
3054
+ description: getIssueTool.description,
3055
+ inputSchema: GetIssueInputSchema,
3056
+ handler: handleGetIssue,
3057
+ annotations: { readOnlyHint: true }
3058
+ },
3059
+ {
3060
+ name: TOOL_NAMES.SEARCH_ISSUES,
3061
+ description: searchIssuesTool.description,
3062
+ inputSchema: SearchIssuesInputSchema,
3063
+ handler: handleSearchIssues,
3064
+ annotations: { readOnlyHint: true }
3065
+ },
3066
+ {
3067
+ name: TOOL_NAMES.GET_MY_ISSUES,
3068
+ description: getMyIssuesTool.description,
3069
+ inputSchema: GetMyIssuesInputSchema,
3070
+ handler: handleGetMyIssues,
3071
+ annotations: { readOnlyHint: true }
3072
+ },
3073
+ {
3074
+ name: TOOL_NAMES.GET_ISSUE_TYPES,
3075
+ description: getIssueTypesTool.description,
3076
+ inputSchema: GetIssueTypesInputSchema,
3077
+ handler: handleGetIssueTypes,
3078
+ annotations: { readOnlyHint: true }
3079
+ },
3080
+ {
3081
+ name: TOOL_NAMES.GET_USERS,
3082
+ description: getUsersTool.description,
3083
+ inputSchema: GetUsersInputSchema,
3084
+ handler: handleGetUsers,
3085
+ annotations: { readOnlyHint: true }
3086
+ },
3087
+ {
3088
+ name: TOOL_NAMES.GET_PRIORITIES,
3089
+ description: getPrioritiesTool.description,
3090
+ inputSchema: GetPrioritiesInputSchema,
3091
+ handler: handleGetPriorities,
3092
+ annotations: { readOnlyHint: true }
3093
+ },
3094
+ {
3095
+ name: TOOL_NAMES.GET_STATUSES,
3096
+ description: getStatusesTool.description,
3097
+ inputSchema: GetStatusesInputSchema,
3098
+ handler: handleGetStatuses,
3099
+ annotations: { readOnlyHint: true }
3100
+ },
3101
+ {
3102
+ name: TOOL_NAMES.GET_PROJECT_INFO,
3103
+ description: getProjectInfoTool.description,
3104
+ inputSchema: GetProjectInfoInputSchema,
3105
+ handler: handleGetProjectInfo,
3106
+ annotations: { readOnlyHint: true }
3107
+ },
3108
+ {
3109
+ name: TOOL_NAMES.GET_CREATE_META,
3110
+ description: getCreateMetaTool.description,
3111
+ inputSchema: GetCreateMetaInputSchema,
3112
+ handler: handleGetCreateMeta,
3113
+ annotations: { readOnlyHint: true }
3114
+ },
3115
+ {
3116
+ name: TOOL_NAMES.GET_CUSTOM_FIELDS,
3117
+ description: getCustomFieldsTool.description,
3118
+ inputSchema: GetCustomFieldsInputSchema,
3119
+ handler: handleGetCustomFields,
3120
+ annotations: { readOnlyHint: true }
3121
+ },
3122
+ {
3123
+ name: TOOL_NAMES.GET_COMMENTS,
3124
+ description: getCommentsTool.description,
3125
+ inputSchema: GetCommentsInputSchema,
3126
+ handler: handleGetComments,
3127
+ annotations: { readOnlyHint: true }
3128
+ },
3129
+ {
3130
+ name: TOOL_NAMES.GET_ATTACHMENTS,
3131
+ description: getAttachmentsTool.description,
3132
+ inputSchema: GetAttachmentsInputSchema,
3133
+ handler: handleGetAttachments,
3134
+ annotations: { readOnlyHint: true }
3135
+ },
3136
+ {
3137
+ name: TOOL_NAMES.GET_TRANSITIONS,
3138
+ description: getTransitionsTool.description,
3139
+ inputSchema: GetTransitionsInputSchema,
3140
+ handler: handleGetTransitions,
3141
+ annotations: { readOnlyHint: true }
3142
+ },
3143
+ // === Mutating tools ===
3144
+ {
3145
+ name: TOOL_NAMES.CREATE_ISSUE,
3146
+ description: createIssueTool.description,
3147
+ inputSchema: CreateIssueInputSchema,
3148
+ handler: handleCreateIssue,
3149
+ annotations: { readOnlyHint: false }
3150
+ },
3151
+ {
3152
+ name: TOOL_NAMES.UPDATE_ISSUE,
3153
+ description: updateIssueTool.description,
3154
+ inputSchema: UpdateIssueInputSchema,
3155
+ handler: handleUpdateIssue,
3156
+ annotations: { readOnlyHint: false }
3157
+ },
3158
+ {
3159
+ name: TOOL_NAMES.ADD_COMMENT,
3160
+ description: addCommentTool.description,
3161
+ inputSchema: AddCommentInputSchema,
3162
+ handler: handleAddComment,
3163
+ annotations: { readOnlyHint: false }
3164
+ },
3165
+ {
3166
+ name: TOOL_NAMES.CREATE_SUBTASK,
3167
+ description: createSubtaskTool.description,
3168
+ inputSchema: CreateSubtaskInputSchema,
3169
+ handler: handleCreateSubtask,
3170
+ annotations: { readOnlyHint: false }
3171
+ },
3172
+ {
3173
+ name: TOOL_NAMES.CREATE_ISSUE_LINK,
3174
+ description: createIssueLinkTool.description,
3175
+ inputSchema: CreateIssueLinkInputSchema,
3176
+ handler: handleCreateIssueLink,
3177
+ annotations: { readOnlyHint: false }
3178
+ },
3179
+ {
3180
+ name: TOOL_NAMES.ADD_ATTACHMENT,
3181
+ description: addAttachmentTool.description,
3182
+ inputSchema: AddAttachmentInputSchema,
3183
+ handler: handleAddAttachment,
3184
+ annotations: { readOnlyHint: false }
3185
+ },
3186
+ {
3187
+ name: TOOL_NAMES.TRANSITION_ISSUE,
3188
+ description: transitionIssueTool.description,
3189
+ inputSchema: TransitionIssueInputSchema,
3190
+ handler: handleTransitionIssue,
3191
+ annotations: { readOnlyHint: false }
3192
+ },
3193
+ {
3194
+ name: TOOL_NAMES.GET_ISSUE_GRAPH,
3195
+ description: getIssueGraphTool.description,
3196
+ inputSchema: GetIssueGraphInputSchema,
3197
+ handler: handleGetIssueGraph,
3198
+ annotations: { readOnlyHint: true }
3199
+ },
3200
+ // === Destructive tools ===
3201
+ {
3202
+ name: TOOL_NAMES.DELETE_ATTACHMENT,
3203
+ description: deleteAttachmentTool.description,
3204
+ inputSchema: DeleteAttachmentInputSchema,
3205
+ handler: handleDeleteAttachment,
3206
+ annotations: { readOnlyHint: false, destructiveHint: true }
3207
+ }
3185
3208
  ];
3186
3209
  async function main() {
3187
3210
  const isDryRun = (process.env.DRY_RUN || "").toLowerCase() === "1" || (process.env.DRY_RUN || "").toLowerCase() === "true";
@@ -3209,50 +3232,45 @@ async function main() {
3209
3232
  version = pkg.default?.version || pkg.version || version;
3210
3233
  } catch {
3211
3234
  }
3212
- const server = new Server(
3235
+ const server = new McpServer(
3213
3236
  {
3214
3237
  name: "jira-mcp-server",
3215
3238
  version
3216
3239
  },
3217
3240
  {
3218
3241
  capabilities: {
3219
- resources: {},
3220
3242
  tools: {}
3221
3243
  }
3222
3244
  }
3223
3245
  );
3224
- server.setRequestHandler(ListToolsRequestSchema, async () => {
3225
- console.error(`\u{1F4CB} Listing ${allTools.length} available tool(s)`);
3226
- return { tools: allTools };
3227
- });
3228
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
3229
- const { name, arguments: args } = request.params;
3230
- console.error(`\u{1F6E0}\uFE0F Executing tool: ${name}`);
3231
- const handler = toolHandlers.get(name);
3232
- if (!handler) {
3233
- throw new Error(`Unknown tool: ${name}`);
3234
- }
3235
- try {
3236
- const result = await handler(args);
3237
- console.error(`\u2705 Tool ${name} executed successfully`);
3238
- return result;
3239
- } catch (error) {
3240
- console.error(`\u274C Error executing tool ${name}:`, error.message);
3241
- throw error;
3242
- }
3243
- });
3244
- server.setRequestHandler(ListResourcesRequestSchema, async () => {
3245
- return { resources: [] };
3246
- });
3247
- server.setRequestHandler(ReadResourceRequestSchema, async () => {
3248
- throw new Error("Resource reading not implemented");
3249
- });
3246
+ for (const entry of toolRegistry) {
3247
+ server.registerTool(
3248
+ entry.name,
3249
+ {
3250
+ description: entry.description,
3251
+ inputSchema: entry.inputSchema,
3252
+ annotations: entry.annotations
3253
+ },
3254
+ async (args) => {
3255
+ console.error(`\u{1F6E0}\uFE0F Executing tool: ${entry.name}`);
3256
+ try {
3257
+ const result = await entry.handler(args);
3258
+ console.error(`\u2705 Tool ${entry.name} executed successfully`);
3259
+ return result;
3260
+ } catch (error) {
3261
+ console.error(`\u274C Error executing tool ${entry.name}:`, error.message);
3262
+ throw error;
3263
+ }
3264
+ }
3265
+ );
3266
+ }
3267
+ console.error(`\u{1F4CB} Registered ${toolRegistry.length} tool(s)`);
3250
3268
  const transport = new StdioServerTransport();
3251
3269
  await server.connect(transport);
3252
3270
  console.error("\u{1F680} Jira MCP server running on stdio");
3253
3271
  }
3254
3272
  main().catch((error) => {
3255
- log23.error("Fatal error:", error);
3273
+ log26.error("Fatal error:", error);
3256
3274
  process.exit(1);
3257
3275
  });
3258
3276
  //# sourceMappingURL=index.js.map