@crypto512/jicon-mcp 2.2.1 → 2.3.19

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.
Files changed (228) hide show
  1. package/PROMPT.md +10 -28
  2. package/README.md +39 -6
  3. package/TOOL_LIST.md +542 -407
  4. package/dist/config/constants.d.ts +28 -0
  5. package/dist/config/constants.d.ts.map +1 -1
  6. package/dist/config/constants.js +34 -0
  7. package/dist/config/constants.js.map +1 -1
  8. package/dist/config/loader.d.ts.map +1 -1
  9. package/dist/config/loader.js +12 -2
  10. package/dist/config/loader.js.map +1 -1
  11. package/dist/config/types.d.ts +49 -6
  12. package/dist/config/types.d.ts.map +1 -1
  13. package/dist/config/types.js +18 -0
  14. package/dist/config/types.js.map +1 -1
  15. package/dist/confluence/client.d.ts.map +1 -1
  16. package/dist/confluence/client.js +7 -2
  17. package/dist/confluence/client.js.map +1 -1
  18. package/dist/confluence/formatters.d.ts +20 -0
  19. package/dist/confluence/formatters.d.ts.map +1 -1
  20. package/dist/confluence/formatters.js +74 -8
  21. package/dist/confluence/formatters.js.map +1 -1
  22. package/dist/confluence/tools.d.ts +16 -20
  23. package/dist/confluence/tools.d.ts.map +1 -1
  24. package/dist/confluence/tools.js +71 -68
  25. package/dist/confluence/tools.js.map +1 -1
  26. package/dist/credentials/extractor.d.ts +15 -5
  27. package/dist/credentials/extractor.d.ts.map +1 -1
  28. package/dist/credentials/extractor.js +99 -12
  29. package/dist/credentials/extractor.js.map +1 -1
  30. package/dist/credentials/index.d.ts +4 -3
  31. package/dist/credentials/index.d.ts.map +1 -1
  32. package/dist/credentials/index.js +3 -2
  33. package/dist/credentials/index.js.map +1 -1
  34. package/dist/credentials/types.d.ts +22 -0
  35. package/dist/credentials/types.d.ts.map +1 -1
  36. package/dist/credentials/types.js.map +1 -1
  37. package/dist/index.js +211 -176
  38. package/dist/index.js.map +1 -1
  39. package/dist/jira/activity-tools.d.ts +8 -15
  40. package/dist/jira/activity-tools.d.ts.map +1 -1
  41. package/dist/jira/activity-tools.js +131 -90
  42. package/dist/jira/activity-tools.js.map +1 -1
  43. package/dist/jira/client.d.ts +24 -0
  44. package/dist/jira/client.d.ts.map +1 -1
  45. package/dist/jira/client.js +65 -6
  46. package/dist/jira/client.js.map +1 -1
  47. package/dist/jira/formatters.d.ts +61 -0
  48. package/dist/jira/formatters.d.ts.map +1 -1
  49. package/dist/jira/formatters.js +83 -11
  50. package/dist/jira/formatters.js.map +1 -1
  51. package/dist/jira/tools.d.ts +78 -26
  52. package/dist/jira/tools.d.ts.map +1 -1
  53. package/dist/jira/tools.js +293 -130
  54. package/dist/jira/tools.js.map +1 -1
  55. package/dist/permissions/filter.d.ts.map +1 -1
  56. package/dist/permissions/filter.js +8 -4
  57. package/dist/permissions/filter.js.map +1 -1
  58. package/dist/permissions/tool-registry.d.ts +15 -13
  59. package/dist/permissions/tool-registry.d.ts.map +1 -1
  60. package/dist/permissions/tool-registry.js +19 -10
  61. package/dist/permissions/tool-registry.js.map +1 -1
  62. package/dist/session/context.d.ts +81 -0
  63. package/dist/session/context.d.ts.map +1 -0
  64. package/dist/session/context.js +107 -0
  65. package/dist/session/context.js.map +1 -0
  66. package/dist/session/index.d.ts +12 -0
  67. package/dist/session/index.d.ts.map +1 -0
  68. package/dist/session/index.js +22 -0
  69. package/dist/session/index.js.map +1 -0
  70. package/dist/session/manager.d.ts +186 -0
  71. package/dist/session/manager.d.ts.map +1 -0
  72. package/dist/session/manager.js +383 -0
  73. package/dist/session/manager.js.map +1 -0
  74. package/dist/tempo/client.d.ts +14 -0
  75. package/dist/tempo/client.d.ts.map +1 -1
  76. package/dist/tempo/client.js +57 -0
  77. package/dist/tempo/client.js.map +1 -1
  78. package/dist/tempo/formatters.d.ts +13 -0
  79. package/dist/tempo/formatters.d.ts.map +1 -1
  80. package/dist/tempo/formatters.js +106 -20
  81. package/dist/tempo/formatters.js.map +1 -1
  82. package/dist/tempo/tools.d.ts +14 -13
  83. package/dist/tempo/tools.d.ts.map +1 -1
  84. package/dist/tempo/tools.js +203 -33
  85. package/dist/tempo/tools.js.map +1 -1
  86. package/dist/tempo/types.d.ts +20 -6
  87. package/dist/tempo/types.d.ts.map +1 -1
  88. package/dist/transport/http.d.ts +21 -5
  89. package/dist/transport/http.d.ts.map +1 -1
  90. package/dist/transport/http.js +193 -22
  91. package/dist/transport/http.js.map +1 -1
  92. package/dist/transport/index.d.ts +7 -2
  93. package/dist/transport/index.d.ts.map +1 -1
  94. package/dist/transport/index.js +10 -4
  95. package/dist/transport/index.js.map +1 -1
  96. package/dist/utils/buffer-tools.d.ts +48 -724
  97. package/dist/utils/buffer-tools.d.ts.map +1 -1
  98. package/dist/utils/buffer-tools.js +337 -170
  99. package/dist/utils/buffer-tools.js.map +1 -1
  100. package/dist/utils/content-buffer.d.ts +10 -31
  101. package/dist/utils/content-buffer.d.ts.map +1 -1
  102. package/dist/utils/content-buffer.js +12 -86
  103. package/dist/utils/content-buffer.js.map +1 -1
  104. package/dist/utils/http-client.d.ts.map +1 -1
  105. package/dist/utils/http-client.js +99 -2
  106. package/dist/utils/http-client.js.map +1 -1
  107. package/dist/utils/jicon-help.d.ts +3 -3
  108. package/dist/utils/jicon-help.d.ts.map +1 -1
  109. package/dist/utils/jicon-help.js +164 -312
  110. package/dist/utils/jicon-help.js.map +1 -1
  111. package/dist/utils/logger.d.ts +43 -0
  112. package/dist/utils/logger.d.ts.map +1 -0
  113. package/dist/utils/logger.js +102 -0
  114. package/dist/utils/logger.js.map +1 -0
  115. package/dist/utils/plantuml/tools.d.ts.map +1 -1
  116. package/dist/utils/plantuml/tools.js +10 -9
  117. package/dist/utils/plantuml/tools.js.map +1 -1
  118. package/dist/utils/response-formatter.d.ts +20 -2
  119. package/dist/utils/response-formatter.d.ts.map +1 -1
  120. package/dist/utils/response-formatter.js +147 -17
  121. package/dist/utils/response-formatter.js.map +1 -1
  122. package/dist/utils/sandbox/formatters.d.ts +25 -0
  123. package/dist/utils/sandbox/formatters.d.ts.map +1 -0
  124. package/dist/utils/sandbox/formatters.js +690 -0
  125. package/dist/utils/sandbox/formatters.js.map +1 -0
  126. package/dist/utils/sandbox/helpers.d.ts +16 -0
  127. package/dist/utils/sandbox/helpers.d.ts.map +1 -0
  128. package/dist/utils/sandbox/helpers.js +252 -0
  129. package/dist/utils/sandbox/helpers.js.map +1 -0
  130. package/dist/utils/sandbox/index.d.ts +19 -0
  131. package/dist/utils/sandbox/index.d.ts.map +1 -0
  132. package/dist/utils/sandbox/index.js +269 -0
  133. package/dist/utils/sandbox/index.js.map +1 -0
  134. package/dist/utils/sandbox/schema.d.ts +55 -0
  135. package/dist/utils/sandbox/schema.d.ts.map +1 -0
  136. package/dist/utils/sandbox/schema.js +39 -0
  137. package/dist/utils/sandbox/schema.js.map +1 -0
  138. package/dist/utils/sandbox/types.d.ts +179 -0
  139. package/dist/utils/sandbox/types.d.ts.map +1 -0
  140. package/dist/utils/sandbox/types.js +8 -0
  141. package/dist/utils/sandbox/types.js.map +1 -0
  142. package/dist/utils/schemas/confluence.d.ts +41 -0
  143. package/dist/utils/schemas/confluence.d.ts.map +1 -0
  144. package/dist/utils/schemas/confluence.js +105 -0
  145. package/dist/utils/schemas/confluence.js.map +1 -0
  146. package/dist/utils/schemas/index.d.ts +77 -0
  147. package/dist/utils/schemas/index.d.ts.map +1 -0
  148. package/dist/utils/schemas/index.js +107 -0
  149. package/dist/utils/schemas/index.js.map +1 -0
  150. package/dist/utils/schemas/jira.d.ts +49 -0
  151. package/dist/utils/schemas/jira.d.ts.map +1 -0
  152. package/dist/utils/schemas/jira.js +153 -0
  153. package/dist/utils/schemas/jira.js.map +1 -0
  154. package/dist/utils/schemas/tempo.d.ts +29 -0
  155. package/dist/utils/schemas/tempo.d.ts.map +1 -0
  156. package/dist/utils/schemas/tempo.js +72 -0
  157. package/dist/utils/schemas/tempo.js.map +1 -0
  158. package/dist/utils/whoami-tools.d.ts +17 -0
  159. package/dist/utils/whoami-tools.d.ts.map +1 -0
  160. package/dist/utils/whoami-tools.js +90 -0
  161. package/dist/utils/whoami-tools.js.map +1 -0
  162. package/dist/utils/xhtml/error-locator.js +5 -5
  163. package/dist/utils/xhtml/error-locator.js.map +1 -1
  164. package/package.json +10 -9
  165. package/dist/credentials/client-factory.d.ts +0 -64
  166. package/dist/credentials/client-factory.d.ts.map +0 -1
  167. package/dist/credentials/client-factory.js +0 -110
  168. package/dist/credentials/client-factory.js.map +0 -1
  169. package/dist/credentials/context.d.ts +0 -25
  170. package/dist/credentials/context.d.ts.map +0 -1
  171. package/dist/credentials/context.js +0 -35
  172. package/dist/credentials/context.js.map +0 -1
  173. package/dist/utils/buffer-pipeline/index.d.ts +0 -30
  174. package/dist/utils/buffer-pipeline/index.d.ts.map +0 -1
  175. package/dist/utils/buffer-pipeline/index.js +0 -317
  176. package/dist/utils/buffer-pipeline/index.js.map +0 -1
  177. package/dist/utils/buffer-pipeline/output/csv.d.ts +0 -20
  178. package/dist/utils/buffer-pipeline/output/csv.d.ts.map +0 -1
  179. package/dist/utils/buffer-pipeline/output/csv.js +0 -117
  180. package/dist/utils/buffer-pipeline/output/csv.js.map +0 -1
  181. package/dist/utils/buffer-pipeline/output/json.d.ts +0 -16
  182. package/dist/utils/buffer-pipeline/output/json.d.ts.map +0 -1
  183. package/dist/utils/buffer-pipeline/output/json.js +0 -48
  184. package/dist/utils/buffer-pipeline/output/json.js.map +0 -1
  185. package/dist/utils/buffer-pipeline/output/markdown.d.ts +0 -15
  186. package/dist/utils/buffer-pipeline/output/markdown.d.ts.map +0 -1
  187. package/dist/utils/buffer-pipeline/output/markdown.js +0 -105
  188. package/dist/utils/buffer-pipeline/output/markdown.js.map +0 -1
  189. package/dist/utils/buffer-pipeline/output/xhtml-list.d.ts +0 -16
  190. package/dist/utils/buffer-pipeline/output/xhtml-list.d.ts.map +0 -1
  191. package/dist/utils/buffer-pipeline/output/xhtml-list.js +0 -81
  192. package/dist/utils/buffer-pipeline/output/xhtml-list.js.map +0 -1
  193. package/dist/utils/buffer-pipeline/output/xhtml-table.d.ts +0 -15
  194. package/dist/utils/buffer-pipeline/output/xhtml-table.d.ts.map +0 -1
  195. package/dist/utils/buffer-pipeline/output/xhtml-table.js +0 -176
  196. package/dist/utils/buffer-pipeline/output/xhtml-table.js.map +0 -1
  197. package/dist/utils/buffer-pipeline/schema.d.ts +0 -1878
  198. package/dist/utils/buffer-pipeline/schema.d.ts.map +0 -1
  199. package/dist/utils/buffer-pipeline/schema.js +0 -168
  200. package/dist/utils/buffer-pipeline/schema.js.map +0 -1
  201. package/dist/utils/buffer-pipeline/stages/filter.d.ts +0 -32
  202. package/dist/utils/buffer-pipeline/stages/filter.d.ts.map +0 -1
  203. package/dist/utils/buffer-pipeline/stages/filter.js +0 -208
  204. package/dist/utils/buffer-pipeline/stages/filter.js.map +0 -1
  205. package/dist/utils/buffer-pipeline/stages/format.d.ts +0 -45
  206. package/dist/utils/buffer-pipeline/stages/format.d.ts.map +0 -1
  207. package/dist/utils/buffer-pipeline/stages/format.js +0 -160
  208. package/dist/utils/buffer-pipeline/stages/format.js.map +0 -1
  209. package/dist/utils/buffer-pipeline/stages/group-by.d.ts +0 -25
  210. package/dist/utils/buffer-pipeline/stages/group-by.d.ts.map +0 -1
  211. package/dist/utils/buffer-pipeline/stages/group-by.js +0 -190
  212. package/dist/utils/buffer-pipeline/stages/group-by.js.map +0 -1
  213. package/dist/utils/buffer-pipeline/stages/select.d.ts +0 -54
  214. package/dist/utils/buffer-pipeline/stages/select.d.ts.map +0 -1
  215. package/dist/utils/buffer-pipeline/stages/select.js +0 -228
  216. package/dist/utils/buffer-pipeline/stages/select.js.map +0 -1
  217. package/dist/utils/buffer-pipeline/stages/sort.d.ts +0 -20
  218. package/dist/utils/buffer-pipeline/stages/sort.d.ts.map +0 -1
  219. package/dist/utils/buffer-pipeline/stages/sort.js +0 -96
  220. package/dist/utils/buffer-pipeline/stages/sort.js.map +0 -1
  221. package/dist/utils/buffer-pipeline/types.d.ts +0 -277
  222. package/dist/utils/buffer-pipeline/types.d.ts.map +0 -1
  223. package/dist/utils/buffer-pipeline/types.js +0 -8
  224. package/dist/utils/buffer-pipeline/types.js.map +0 -1
  225. package/dist/utils/plantuml/docker-manager.d.ts +0 -37
  226. package/dist/utils/plantuml/docker-manager.d.ts.map +0 -1
  227. package/dist/utils/plantuml/docker-manager.js +0 -284
  228. package/dist/utils/plantuml/docker-manager.js.map +0 -1
@@ -7,11 +7,16 @@
7
7
  * - "Any blockers in recent discussions?"
8
8
  * - "Summarize team activity this month"
9
9
  * - "Analyze Epic PROJ-123 for progress and velocity"
10
+ *
11
+ * All tool handlers use session-scoped clients via getSessionJiraClient()
12
+ * which provides per-session caching and credential isolation.
10
13
  */
11
14
  import { z } from "zod";
12
- import { formatSuccessDirect, formatError, isApiError, getMaxOutputSize } from "../utils/response-formatter.js";
15
+ import { getSessionJiraClient } from "../session/context.js";
16
+ import { formatSuccessDirect, formatSuccessJson, formatError, isApiError, getMaxOutputSize, getMaxApiResults, getDefaultPeriod } from "../utils/response-formatter.js";
13
17
  import { parseSince } from "../utils/date-tools.js";
14
18
  import { formatTimeSpent } from "../utils/time-formatter.js";
19
+ import { formatCommentMetadata } from "./formatters.js";
15
20
  /**
16
21
  * Build JQL from flexible scoping parameters
17
22
  */
@@ -25,7 +30,7 @@ function buildScopingJql(params) {
25
30
  if (params.projectKey) {
26
31
  return `project = ${params.projectKey}`;
27
32
  }
28
- throw new Error("At least one scoping parameter required: projectKey, jql, or issueKeys");
33
+ throw new Error('At least one scoping parameter required: projectKey, jql, or issueKeys. Call help(topic="analysis") for scoping guide.');
29
34
  }
30
35
  /**
31
36
  * Format a date string for display (e.g., "Jan 28 14:30")
@@ -82,11 +87,73 @@ function truncateText(text, maxLength) {
82
87
  return text.substring(0, maxLength - 3) + "...";
83
88
  }
84
89
  /**
85
- * Create activity tools with a client getter function
90
+ * Sanitize text for use in markdown tables.
91
+ * Removes/replaces characters that break table formatting.
86
92
  */
87
- export function createActivityTools(getClient) {
93
+ function sanitizeForTable(text) {
94
+ if (!text)
95
+ return "";
96
+ return text
97
+ .replace(/<br\s*\/?>/gi, " ") // Replace <br> tags with space
98
+ .replace(/<[^>]+>/g, "") // Remove other HTML tags
99
+ .replace(/\|/g, "\\|") // Escape pipe characters
100
+ .replace(/\n/g, " ") // Replace newlines with space
101
+ .replace(/\s+/g, " ") // Collapse multiple spaces
102
+ .trim();
103
+ }
104
+ /**
105
+ * Convert HTML content to Markdown for free-text display (comments, descriptions).
106
+ * Preserves line breaks and basic formatting.
107
+ */
108
+ function htmlToMarkdown(text) {
109
+ if (!text)
110
+ return "";
111
+ return text
112
+ // Convert block elements to double newlines
113
+ .replace(/<\/p>\s*<p[^>]*>/gi, "\n\n")
114
+ .replace(/<p[^>]*>/gi, "")
115
+ .replace(/<\/p>/gi, "\n\n")
116
+ .replace(/<\/div>\s*<div[^>]*>/gi, "\n\n")
117
+ .replace(/<div[^>]*>/gi, "")
118
+ .replace(/<\/div>/gi, "\n")
119
+ // Convert line breaks
120
+ .replace(/<br\s*\/?>/gi, "\n")
121
+ // Convert lists
122
+ .replace(/<li[^>]*>/gi, "• ")
123
+ .replace(/<\/li>/gi, "\n")
124
+ .replace(/<\/?[uo]l[^>]*>/gi, "\n")
125
+ // Convert basic formatting
126
+ .replace(/<strong[^>]*>|<b[^>]*>/gi, "**")
127
+ .replace(/<\/strong>|<\/b>/gi, "**")
128
+ .replace(/<em[^>]*>|<i[^>]*>/gi, "_")
129
+ .replace(/<\/em>|<\/i>/gi, "_")
130
+ .replace(/<code[^>]*>/gi, "`")
131
+ .replace(/<\/code>/gi, "`")
132
+ // Convert links: <a href="url">text</a> → [text](url)
133
+ .replace(/<a[^>]*href="([^"]*)"[^>]*>([^<]*)<\/a>/gi, "[$2]($1)")
134
+ // Remove remaining HTML tags
135
+ .replace(/<[^>]+>/g, "")
136
+ // Decode common HTML entities
137
+ .replace(/&amp;/g, "&")
138
+ .replace(/&lt;/g, "<")
139
+ .replace(/&gt;/g, ">")
140
+ .replace(/&quot;/g, '"')
141
+ .replace(/&#39;/g, "'")
142
+ .replace(/&nbsp;/g, " ")
143
+ // Clean up excessive whitespace while preserving intentional line breaks
144
+ .replace(/[ \t]+/g, " ") // Collapse horizontal whitespace
145
+ .replace(/\n{3,}/g, "\n\n") // Max 2 consecutive newlines
146
+ .trim();
147
+ }
148
+ /**
149
+ * Create activity tools using session-scoped clients
150
+ *
151
+ * Tools automatically use the JiraClient from the current session context,
152
+ * which provides per-session caching and credential isolation.
153
+ */
154
+ export function createActivityTools() {
88
155
  const requireClient = () => {
89
- const client = getClient();
156
+ const client = getSessionJiraClient();
90
157
  if (!client) {
91
158
  throw new Error("Jira is not configured. Provide JIRA_URL and JIRA_API_TOKEN environment variables, or pass credentials via X-Jira-Url and X-Jira-Token headers.");
92
159
  }
@@ -104,14 +171,14 @@ Fetches comments, status changes, priority changes, and assignee changes. Ideal
104
171
  REQUIRES: At least one scoping parameter (projectKey, jql, or issueKeys)
105
172
  RETURNS: Markdown-formatted activity digest with pre-computed summary
106
173
 
107
- Example: jira_get_activity_digest(projectKey="ACME", since="7d")`,
174
+ Example: jira_get_activity_digest(projectKey="ACME", since="30d")`,
108
175
  inputSchema: z.object({
109
176
  // Flexible scoping - at least one required
110
177
  projectKey: z.string().optional().describe("Filter by project key"),
111
178
  jql: z.string().optional().describe("Full JQL query for flexible scoping"),
112
179
  issueKeys: z.array(z.string()).optional().describe("Specific issue keys to include"),
113
180
  // Time range
114
- since: z.string().optional().describe('Time range: "1d", "7d", "30d", or ISO date (default: "7d")'),
181
+ since: z.string().optional().describe('Time range: "7d", "30d", "365d", or ISO date (default: "30d"). Provide explicitly for accurate results.'),
115
182
  // Activity types
116
183
  includeComments: z.boolean().optional().describe("Include comments (default: true)"),
117
184
  includeStatusChanges: z.boolean().optional().describe("Include status changes (default: true)"),
@@ -119,14 +186,12 @@ Example: jira_get_activity_digest(projectKey="ACME", since="7d")`,
119
186
  includeAssigneeChanges: z.boolean().optional().describe("Include assignee changes (default: true)"),
120
187
  // Output control
121
188
  maxActivities: z.number().optional().describe("Maximum activities to return (default: 50)"),
122
- commentMaxLength: z.number().optional().describe("Max length for comment text (default: 500)"),
123
189
  }),
124
190
  handler: async (args) => {
125
191
  try {
126
- const since = args.since || "7d";
192
+ const since = args.since || getDefaultPeriod();
127
193
  const { dateFrom, dateTo } = parseSince(since);
128
194
  const maxActivities = args.maxActivities ?? 50;
129
- const commentMaxLength = args.commentMaxLength ?? 500;
130
195
  // Build scoping JQL
131
196
  const scopeJql = buildScopingJql({
132
197
  projectKey: args.projectKey,
@@ -207,7 +272,7 @@ Example: jira_get_activity_digest(projectKey="ACME", since="7d")`,
207
272
  timestamp: comment.created,
208
273
  author: comment.author.displayName,
209
274
  details: {
210
- body: truncateText(comment.body, commentMaxLength),
275
+ body: htmlToMarkdown(comment.body),
211
276
  },
212
277
  });
213
278
  }
@@ -343,36 +408,36 @@ Example: jira_get_activity_digest(projectKey="ACME", since="7d")`,
343
408
  },
344
409
  },
345
410
  jira_get_recent_comments: {
346
- description: `Get comments across multiple issues for discussion analysis. Returns DIRECTLY (not buffered).
411
+ description: `Get comments across multiple issues for analysis. Returns buffered data.
347
412
 
348
413
  Ideal for:
414
+ - "Who commented the most on project X?"
349
415
  - "Show me recent discussions about authentication"
350
- - "What are people saying about the new feature?"
351
416
  - "Find comments mentioning blockers"
352
417
 
353
418
  REQUIRES: At least one scoping parameter (projectKey, jql, or issueKeys)
354
- RETURNS: Markdown-formatted comments with issue context
419
+ IMPORTANT: Always provide since default is 30d. Use "365d" or ISO date for longer history.
420
+ RETURNS: bufferId with comments array (issueKey, author, body, created)
421
+ NEXT: buffer_transform for stats/tables, buffer_grep to search
355
422
 
356
- Example: jira_get_recent_comments(projectKey="ACME", since="7d")`,
423
+ Example: jira_get_recent_comments(projectKey="ACME", since="365d", maxComments=5000)`,
357
424
  inputSchema: z.object({
358
425
  // Flexible scoping
359
426
  projectKey: z.string().optional().describe("Filter by project key"),
360
427
  jql: z.string().optional().describe("Full JQL query for flexible scoping"),
361
428
  issueKeys: z.array(z.string()).optional().describe("Specific issue keys to include"),
362
429
  // Time
363
- since: z.string().optional().describe('Time range: "1d", "7d", "30d", or ISO date (default: "7d")'),
430
+ since: z.string().optional().describe('Time range: "7d", "30d", "365d", or ISO date (default: "30d"). Provide explicitly for accurate results.'),
364
431
  // Filtering
365
432
  authorKey: z.string().optional().describe("Filter by comment author username"),
366
433
  // Output
367
- maxComments: z.number().optional().describe("Maximum comments to return (default: 30)"),
368
- includeContext: z.boolean().optional().describe("Include issue summary/status (default: true)"),
434
+ maxComments: z.number().optional().describe("Maximum comments to return (default: all)"),
369
435
  }),
370
436
  handler: async (args) => {
371
437
  try {
372
- const since = args.since || "7d";
438
+ const since = args.since || getDefaultPeriod();
373
439
  const { dateFrom, dateTo } = parseSince(since);
374
- const maxComments = args.maxComments ?? 30;
375
- const includeContext = args.includeContext !== false;
440
+ const maxComments = args.maxComments ?? getMaxApiResults();
376
441
  // Build scoping JQL
377
442
  const scopeJql = buildScopingJql({
378
443
  projectKey: args.projectKey,
@@ -382,11 +447,13 @@ Example: jira_get_recent_comments(projectKey="ACME", since="7d")`,
382
447
  // Add date filter for updated issues (comments update the issue)
383
448
  const fullJql = `${scopeJql} AND updated >= "${dateFrom}" ORDER BY updated DESC`;
384
449
  // Search for issues
385
- const searchResult = await requireClient().searchIssuesAll(fullJql, ["summary", "status"], 100);
450
+ const searchResult = await requireClient().searchIssuesAll(fullJql, ["summary", "status"], getMaxApiResults());
386
451
  if (searchResult.issues.length === 0) {
387
- return formatSuccessDirect({
388
- _directContent: true,
389
- content: `# Recent Comments\n\nNo issues with recent activity found for the specified scope.`,
452
+ return formatSuccessJson([], {
453
+ resourceType: "jira_comments",
454
+ schemaType: "jira_comment",
455
+ title: "Recent comments: no issues with recent activity found",
456
+ totalComments: 0,
390
457
  });
391
458
  }
392
459
  // Get comments for all issues
@@ -413,54 +480,28 @@ Example: jira_get_recent_comments(projectKey="ACME", since="7d")`,
413
480
  // Sort by date (most recent first)
414
481
  allComments.sort((a, b) => new Date(b.comment.created).getTime() - new Date(a.comment.created).getTime());
415
482
  const limitedComments = allComments.slice(0, maxComments);
416
- // Group by issue
417
- const byIssue = new Map();
418
- for (const { issueKey, issue, comment } of limitedComments) {
419
- if (!byIssue.has(issueKey)) {
420
- byIssue.set(issueKey, { issue, comments: [] });
421
- }
422
- byIssue.get(issueKey).comments.push(comment);
423
- }
424
- // Build markdown output
483
+ // Flatten comments to jira_comment schema with issue context
484
+ const comments = limitedComments.map(({ issueKey, issue, comment }) => formatCommentMetadata(comment, {
485
+ issueKey,
486
+ issueSummary: issue.fields.summary || undefined,
487
+ }));
425
488
  const scopeDesc = args.projectKey
426
489
  ? `Project ${args.projectKey}`
427
490
  : args.jql
428
491
  ? `JQL: ${args.jql.substring(0, 50)}...`
429
492
  : `${args.issueKeys?.length || 0} issues`;
430
- const lines = [
431
- `# Recent Comments: ${scopeDesc}`,
432
- `**Period**: ${dateFrom} to ${dateTo}`,
433
- "",
434
- ];
435
- // Track unique authors
436
- const authors = new Set();
437
- for (const [issueKey, data] of byIssue) {
438
- const statusName = data.issue.fields.status?.name || "Unknown";
439
- const contextInfo = includeContext ? ` [${statusName}]` : "";
440
- lines.push(`## ${issueKey}: ${data.issue.fields.summary}${contextInfo}`);
441
- for (const comment of data.comments) {
442
- authors.add(comment.author.displayName);
443
- const dateTime = formatDisplayDateTime(comment.created);
444
- // Truncate long comments
445
- const body = truncateText(comment.body, 500);
446
- const formattedBody = body.includes("\n")
447
- ? body.split("\n").map((l, i) => (i === 0 ? l : " " + l)).join("\n")
448
- : body;
449
- lines.push(`- @${comment.author.displayName} (${dateTime}): "${formattedBody}"`);
450
- }
451
- lines.push("");
452
- }
453
- // Add summary
454
- lines.push("---");
455
- lines.push(`**Total**: ${limitedComments.length} comments across ${byIssue.size} issues`);
456
- lines.push(`**Unique authors**: ${authors.size}`);
457
- if (allComments.length > maxComments) {
458
- lines.push(`**Note**: Showing ${maxComments} of ${allComments.length} comments`);
459
- }
460
- const output = lines.join("\n");
461
- return formatSuccessDirect({
462
- _directContent: true,
463
- content: output,
493
+ return formatSuccessJson(comments, {
494
+ resourceType: "jira_comments",
495
+ schemaType: "jira_comment",
496
+ title: `Recent comments: ${scopeDesc} (${dateFrom} to ${dateTo})`,
497
+ totalComments: allComments.length,
498
+ info: {
499
+ period: `${dateFrom} to ${dateTo}`,
500
+ since,
501
+ issuesSearched: searchResult.issues.length,
502
+ totalComments: allComments.length,
503
+ ...(allComments.length > maxComments && { limited: `Showing ${maxComments} of ${allComments.length} (increase maxComments)` }),
504
+ },
464
505
  });
465
506
  }
466
507
  catch (error) {
@@ -479,14 +520,14 @@ Ideal for:
479
520
  REQUIRES: At least one scoping parameter (projectKey, jql, or issueKeys)
480
521
  RETURNS: Markdown-formatted changelog with pre-computed summary
481
522
 
482
- Example: jira_get_changelog(projectKey="ACME", since="7d", fields=["status", "priority"])`,
523
+ Example: jira_get_changelog(projectKey="ACME", since="30d", fields=["status", "priority"])`,
483
524
  inputSchema: z.object({
484
525
  // Flexible scoping
485
526
  projectKey: z.string().optional().describe("Filter by project key"),
486
527
  jql: z.string().optional().describe("Full JQL query for flexible scoping"),
487
528
  issueKeys: z.array(z.string()).optional().describe("Specific issue keys to include"),
488
529
  // Time
489
- since: z.string().optional().describe('Time range: "1d", "7d", "30d", or ISO date (default: "7d")'),
530
+ since: z.string().optional().describe('Time range: "7d", "30d", "365d", or ISO date (default: "30d"). Provide explicitly for accurate results.'),
490
531
  // Filtering
491
532
  fields: z.array(z.string()).optional().describe('Fields to track (default: ["status", "priority", "assignee", "resolution"])'),
492
533
  // Output
@@ -494,7 +535,7 @@ Example: jira_get_changelog(projectKey="ACME", since="7d", fields=["status", "pr
494
535
  }),
495
536
  handler: async (args) => {
496
537
  try {
497
- const since = args.since || "7d";
538
+ const since = args.since || getDefaultPeriod();
498
539
  const { dateFrom, dateTo } = parseSince(since);
499
540
  const userFields = args.fields || ["status", "priority", "assignee", "resolution"];
500
541
  const maxChanges = args.maxChanges ?? 50;
@@ -867,7 +908,7 @@ Example: jira_analyze_epic(issueKey="PROJ-100")`,
867
908
  issueKey,
868
909
  author: comment.author.displayName,
869
910
  date: comment.created,
870
- body: truncateText(comment.body, 300),
911
+ body: htmlToMarkdown(comment.body),
871
912
  });
872
913
  }
873
914
  }
@@ -905,7 +946,7 @@ Example: jira_analyze_epic(issueKey="PROJ-100")`,
905
946
  const description = rootIssue.fields.description;
906
947
  if (description) {
907
948
  lines.push("## Description");
908
- lines.push(truncateText(description, 500));
949
+ lines.push(htmlToMarkdown(description));
909
950
  lines.push("");
910
951
  }
911
952
  // Hierarchy breakdown
@@ -917,7 +958,7 @@ Example: jira_analyze_epic(issueKey="PROJ-100")`,
917
958
  const completion = metrics.total > 0
918
959
  ? Math.round((metrics.done / metrics.total) * 100)
919
960
  : 0;
920
- lines.push(`| ${type} | ${metrics.total} | ${metrics.done} | ${metrics.inProgress} | ${metrics.todo} | ${completion}% |`);
961
+ lines.push(`| ${sanitizeForTable(type)} | ${metrics.total} | ${metrics.done} | ${metrics.inProgress} | ${metrics.todo} | ${completion}% |`);
921
962
  }
922
963
  const totalCompletion = totals.total > 0
923
964
  ? Math.round((totals.done / totals.total) * 100)
@@ -985,7 +1026,7 @@ Example: jira_analyze_epic(issueKey="PROJ-100")`,
985
1026
  lines.push("| Source | Relationship | Target | Project | Status |");
986
1027
  lines.push("|--------|--------------|--------|---------|--------|");
987
1028
  for (const dep of crossProjectDeps.slice(0, 20)) {
988
- lines.push(`| ${dep.source} | ${dep.relationship} | ${dep.target} | ${dep.project} | ${dep.status} |`);
1029
+ lines.push(`| ${dep.source} | ${sanitizeForTable(dep.relationship)} | ${dep.target} | ${dep.project} | ${sanitizeForTable(dep.status)} |`);
989
1030
  }
990
1031
  if (crossProjectDeps.length > 20) {
991
1032
  lines.push("");
@@ -1001,9 +1042,9 @@ Example: jira_analyze_epic(issueKey="PROJ-100")`,
1001
1042
  const shownComments = recentComments.slice(0, 15);
1002
1043
  for (const comment of shownComments) {
1003
1044
  const dateStr = formatDisplayDateTime(comment.date);
1004
- const body = comment.body.replace(/\n/g, " ").trim();
1045
+ // Body already converted via htmlToMarkdown during collection
1005
1046
  lines.push(`- **${comment.issueKey}** @${comment.author} (${dateStr}):`);
1006
- lines.push(` "${body}"`);
1047
+ lines.push(` "${comment.body}"`);
1007
1048
  }
1008
1049
  if (recentComments.length > 15) {
1009
1050
  lines.push(`- *...and ${recentComments.length - 15} more comments*`);
@@ -1228,10 +1269,10 @@ Example: jira_epic_summary(issueKey="PROJ-100")`,
1228
1269
  lines.push("| Key | Type | Summary | Status | Assignee |");
1229
1270
  lines.push("|-----|------|---------|--------|----------|");
1230
1271
  for (const issue of allIssues.slice(0, 50)) {
1231
- const type = issue.fields.issuetype?.name || "?";
1232
- const summary = truncateText(issue.fields.summary || "", 50);
1233
- const status = issue.fields.status?.name || "?";
1234
- const assignee = issue.fields.assignee?.displayName || "Unassigned";
1272
+ const type = sanitizeForTable(issue.fields.issuetype?.name) || "?";
1273
+ const summary = sanitizeForTable(truncateText(issue.fields.summary || "", 50));
1274
+ const status = sanitizeForTable(issue.fields.status?.name) || "?";
1275
+ const assignee = sanitizeForTable(issue.fields.assignee?.displayName) || "Unassigned";
1235
1276
  lines.push(`| ${issue.key} | ${type} | ${summary} | ${status} | ${assignee} |`);
1236
1277
  }
1237
1278
  if (allIssues.length > 50) {
@@ -1246,7 +1287,7 @@ Example: jira_epic_summary(issueKey="PROJ-100")`,
1246
1287
  lines.push("|------|-------|------|-------------|-------|------------|");
1247
1288
  for (const [type, metrics] of byType) {
1248
1289
  const completion = metrics.total > 0 ? Math.round((metrics.done / metrics.total) * 100) : 0;
1249
- lines.push(`| ${type} | ${metrics.total} | ${metrics.done} | ${metrics.inProgress} | ${metrics.todo} | ${completion}% |`);
1290
+ lines.push(`| ${sanitizeForTable(type)} | ${metrics.total} | ${metrics.done} | ${metrics.inProgress} | ${metrics.todo} | ${completion}% |`);
1250
1291
  }
1251
1292
  lines.push(`| **TOTAL** | **${totalIssues}** | **${totalDone}** | **${totalInProgress}** | **${totalTodo}** | **${totalCompletion}%** |`);
1252
1293
  lines.push("");
@@ -1257,7 +1298,7 @@ Example: jira_epic_summary(issueKey="PROJ-100")`,
1257
1298
  lines.push("| Source | Relationship | Target | Project | Status |");
1258
1299
  lines.push("|--------|--------------|--------|---------|--------|");
1259
1300
  for (const dep of crossProjectDeps.slice(0, 20)) {
1260
- lines.push(`| ${dep.source} | ${dep.relationship} | ${dep.target} | ${dep.project} | ${dep.status} |`);
1301
+ lines.push(`| ${dep.source} | ${sanitizeForTable(dep.relationship)} | ${dep.target} | ${dep.project} | ${sanitizeForTable(dep.status)} |`);
1261
1302
  }
1262
1303
  if (crossProjectDeps.length > 20) {
1263
1304
  lines.push("");
@@ -1483,7 +1524,7 @@ Example: jira_analyze_sprint(sprintId=123)`,
1483
1524
  issueKey,
1484
1525
  author: comment.author.displayName,
1485
1526
  date: comment.created,
1486
- body: truncateText(comment.body, 200),
1527
+ body: htmlToMarkdown(comment.body),
1487
1528
  });
1488
1529
  }
1489
1530
  }
@@ -1522,7 +1563,7 @@ Example: jira_analyze_sprint(sprintId=123)`,
1522
1563
  lines.push("| Type | Total | Done | In Progress | To Do |");
1523
1564
  lines.push("|------|-------|------|-------------|-------|");
1524
1565
  for (const [type, metrics] of byType) {
1525
- lines.push(`| ${type} | ${metrics.total} | ${metrics.done} | ${metrics.inProgress} | ${metrics.todo} |`);
1566
+ lines.push(`| ${sanitizeForTable(type)} | ${metrics.total} | ${metrics.done} | ${metrics.inProgress} | ${metrics.todo} |`);
1526
1567
  }
1527
1568
  lines.push("");
1528
1569
  // Time metrics
@@ -1549,7 +1590,7 @@ Example: jira_analyze_sprint(sprintId=123)`,
1549
1590
  // Sort by total issues descending
1550
1591
  const sortedAssignees = Array.from(byAssignee.entries()).sort((a, b) => b[1].total - a[1].total);
1551
1592
  for (const [assignee, metrics] of sortedAssignees.slice(0, 15)) {
1552
- lines.push(`| ${assignee} | ${metrics.total} | ${metrics.done} | ${metrics.inProgress} |`);
1593
+ lines.push(`| ${sanitizeForTable(assignee)} | ${metrics.total} | ${metrics.done} | ${metrics.inProgress} |`);
1553
1594
  }
1554
1595
  if (sortedAssignees.length > 15) {
1555
1596
  lines.push(`| *...and ${sortedAssignees.length - 15} more* | | | |`);
@@ -1561,7 +1602,7 @@ Example: jira_analyze_sprint(sprintId=123)`,
1561
1602
  lines.push("");
1562
1603
  lines.push(`⚠️ **${risks.length} issue${risks.length > 1 ? "s" : ""} at risk:**`);
1563
1604
  for (const risk of risks.slice(0, 10)) {
1564
- lines.push(`- **${risk.issueKey}**: ${risk.detail}`);
1605
+ lines.push(`- **${risk.issueKey}**: ${sanitizeForTable(risk.detail)}`);
1565
1606
  }
1566
1607
  if (risks.length > 10) {
1567
1608
  lines.push(`- *...and ${risks.length - 10} more risks*`);
@@ -1580,8 +1621,8 @@ Example: jira_analyze_sprint(sprintId=123)`,
1580
1621
  lines.push("");
1581
1622
  for (const comment of recentComments.slice(0, 10)) {
1582
1623
  const dateStr = formatDisplayDate(comment.date);
1583
- const body = comment.body.replace(/\n/g, " ").trim();
1584
- lines.push(`- **${comment.issueKey}** @${comment.author} (${dateStr}): "${body}"`);
1624
+ // Body already converted via htmlToMarkdown during collection
1625
+ lines.push(`- **${comment.issueKey}** @${comment.author} (${dateStr}): "${comment.body}"`);
1585
1626
  }
1586
1627
  if (recentComments.length > 10) {
1587
1628
  lines.push(`- *...and ${recentComments.length - 10} more comments*`);
@@ -1592,7 +1633,7 @@ Example: jira_analyze_sprint(sprintId=123)`,
1592
1633
  lines.push("---");
1593
1634
  lines.push("## Summary");
1594
1635
  lines.push("");
1595
- lines.push(`Sprint "${sprint.name}" is **${completionPercent}% complete** with ${statusCounts.inProgress} issues in progress and ${statusCounts.todo} issues remaining.`);
1636
+ lines.push(`Sprint "${sanitizeForTable(sprint.name)}" is **${completionPercent}% complete** with ${statusCounts.inProgress} issues in progress and ${statusCounts.todo} issues remaining.`);
1596
1637
  if (daysRemaining > 0) {
1597
1638
  lines.push(`**${daysRemaining} days remaining** in the sprint.`);
1598
1639
  }