@geeveeh/atlassian-tools 0.2.0 → 0.3.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.
@@ -1,5 +1,26 @@
1
+ import { readFile } from "fs/promises";
2
+ import { basename, extname } from "path";
1
3
  import { AtlassianClient } from "../core/client.js";
2
4
  import { textToAdf, adfToText, buildJql } from "./helpers.js";
5
+ function attachmentMimeType(filePath) {
6
+ const map = {
7
+ ".png": "image/png",
8
+ ".jpg": "image/jpeg",
9
+ ".jpeg": "image/jpeg",
10
+ ".gif": "image/gif",
11
+ ".webp": "image/webp",
12
+ ".svg": "image/svg+xml",
13
+ ".pdf": "application/pdf",
14
+ ".zip": "application/zip",
15
+ ".txt": "text/plain",
16
+ ".csv": "text/csv",
17
+ ".json": "application/json",
18
+ ".xml": "application/xml",
19
+ ".html": "text/html",
20
+ ".md": "text/markdown",
21
+ };
22
+ return map[extname(filePath).toLowerCase()] ?? "application/octet-stream";
23
+ }
3
24
  export class JiraClient {
4
25
  http;
5
26
  constructor(config) {
@@ -44,6 +65,8 @@ export class JiraClient {
44
65
  fields.labels = input.labels;
45
66
  if (input.assigneeId)
46
67
  fields.assignee = { accountId: input.assigneeId };
68
+ if (input.parentKey)
69
+ fields.parent = { key: input.parentKey };
47
70
  const created = await this.http.request("/rest/api/3/issue", { method: "POST", body: JSON.stringify({ fields }) });
48
71
  return this.getIssue(created.key);
49
72
  }
@@ -75,6 +98,71 @@ export class JiraClient {
75
98
  async deleteIssue(issueKey) {
76
99
  await this.http.request(`/rest/api/3/issue/${encodeURIComponent(issueKey)}`, { method: "DELETE" });
77
100
  }
101
+ // ── Worklogs ─────────────────────────────────────────────────────
102
+ async listWorklogs(issueKey) {
103
+ const result = await this.http.request(`/rest/api/3/issue/${encodeURIComponent(issueKey)}/worklog`);
104
+ return result.worklogs;
105
+ }
106
+ async addWorklog(input) {
107
+ const payload = {
108
+ timeSpent: input.timeSpent,
109
+ started: input.started ?? new Date().toISOString().replace("Z", "+0000"),
110
+ };
111
+ if (input.comment)
112
+ payload.comment = textToAdf(input.comment);
113
+ return this.http.request(`/rest/api/3/issue/${encodeURIComponent(input.issueKey)}/worklog`, { method: "POST", body: JSON.stringify(payload) });
114
+ }
115
+ // ── Issue links ──────────────────────────────────────────────────
116
+ async listIssueLinkTypes() {
117
+ const result = await this.http.request("/rest/api/3/issueLinkType");
118
+ return result.issueLinkTypes;
119
+ }
120
+ async listIssueLinks(issueKey) {
121
+ const result = await this.http.request(`/rest/api/3/issue/${encodeURIComponent(issueKey)}?fields=issuelinks`);
122
+ return result.fields.issuelinks ?? [];
123
+ }
124
+ async linkIssues(outwardIssueKey, linkTypeName, inwardIssueKey) {
125
+ await this.http.request("/rest/api/3/issueLink", {
126
+ method: "POST",
127
+ body: JSON.stringify({
128
+ type: { name: linkTypeName },
129
+ outwardIssue: { key: outwardIssueKey },
130
+ inwardIssue: { key: inwardIssueKey },
131
+ }),
132
+ });
133
+ }
134
+ async removeIssueLink(linkId) {
135
+ await this.http.request(`/rest/api/3/issueLink/${encodeURIComponent(linkId)}`, {
136
+ method: "DELETE",
137
+ });
138
+ }
139
+ // ── Comments ─────────────────────────────────────────────────────
140
+ async listComments(issueKey) {
141
+ const result = await this.http.request(`/rest/api/3/issue/${encodeURIComponent(issueKey)}/comment`);
142
+ return result.comments;
143
+ }
144
+ async addComment(issueKey, text) {
145
+ return this.http.request(`/rest/api/3/issue/${encodeURIComponent(issueKey)}/comment`, { method: "POST", body: JSON.stringify({ body: textToAdf(text) }) });
146
+ }
147
+ // ── Attachments ──────────────────────────────────────────────────
148
+ async uploadAttachment(input) {
149
+ const { issueKey, filePath } = input;
150
+ const fileBuffer = await readFile(filePath);
151
+ const fileName = basename(filePath);
152
+ const blob = new Blob([fileBuffer], { type: attachmentMimeType(filePath) });
153
+ const formData = new FormData();
154
+ formData.append("file", blob, fileName);
155
+ const results = await this.http.request(`/rest/api/3/issue/${encodeURIComponent(issueKey)}/attachments`, {
156
+ method: "POST",
157
+ body: formData,
158
+ headers: { "X-Atlassian-Token": "no-check" },
159
+ });
160
+ return results[0];
161
+ }
162
+ async listAttachments(issueKey) {
163
+ const result = await this.http.request(`/rest/api/3/issue/${encodeURIComponent(issueKey)}?fields=attachment`);
164
+ return result.fields.attachment ?? [];
165
+ }
78
166
  // ── Helpers ──────────────────────────────────────────────────────
79
167
  descriptionToText(issue) {
80
168
  return adfToText(issue.fields.description);
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/jira/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/jira/client.ts"],"names":[],"mappings":"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"}
@@ -48,6 +48,7 @@ export interface IssueCreateInput {
48
48
  priority?: string;
49
49
  labels?: string[];
50
50
  assigneeId?: string;
51
+ parentKey?: string;
51
52
  }
52
53
  export interface IssueUpdateInput {
53
54
  issueKey: string;
@@ -65,4 +66,80 @@ export interface IssueSearchOptions {
65
66
  type?: string;
66
67
  limit?: number;
67
68
  }
69
+ export interface JiraAttachment {
70
+ id: string;
71
+ filename: string;
72
+ mimeType: string;
73
+ size: number;
74
+ content: string;
75
+ created?: string;
76
+ author?: {
77
+ displayName: string;
78
+ };
79
+ }
80
+ export interface JiraAttachmentUploadInput {
81
+ issueKey: string;
82
+ filePath: string;
83
+ }
84
+ export interface JiraComment {
85
+ id: string;
86
+ author?: {
87
+ displayName: string;
88
+ accountId: string;
89
+ };
90
+ body?: unknown;
91
+ created: string;
92
+ updated?: string;
93
+ }
94
+ export interface JiraLinkType {
95
+ id: string;
96
+ name: string;
97
+ inward: string;
98
+ outward: string;
99
+ }
100
+ export interface JiraIssueLink {
101
+ id: string;
102
+ type: {
103
+ id: string;
104
+ name: string;
105
+ inward: string;
106
+ outward: string;
107
+ };
108
+ inwardIssue?: {
109
+ id: string;
110
+ key: string;
111
+ fields: {
112
+ summary: string;
113
+ status: {
114
+ name: string;
115
+ };
116
+ };
117
+ };
118
+ outwardIssue?: {
119
+ id: string;
120
+ key: string;
121
+ fields: {
122
+ summary: string;
123
+ status: {
124
+ name: string;
125
+ };
126
+ };
127
+ };
128
+ }
129
+ export interface JiraWorklog {
130
+ id: string;
131
+ author?: {
132
+ displayName: string;
133
+ };
134
+ timeSpent: string;
135
+ timeSpentSeconds: number;
136
+ started: string;
137
+ comment?: unknown;
138
+ }
139
+ export interface WorklogInput {
140
+ issueKey: string;
141
+ timeSpent: string;
142
+ started?: string;
143
+ comment?: string;
144
+ }
68
145
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jira/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QACzB,SAAS,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5B,QAAQ,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5B,QAAQ,CAAC,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QACtD,QAAQ,CAAC,EAAE;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC;QACnC,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jira/types.ts"],"names":[],"mappings":"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"}
package/dist/mcp/index.js CHANGED
@@ -175,6 +175,139 @@ server.tool("confluence_delete_page", "Delete a Confluence page. IMPORTANT: Ask
175
175
  return { content: [{ type: "text", text: formatError(err) }], isError: true };
176
176
  }
177
177
  });
178
+ server.tool("confluence_list_comments", "List comments on a Confluence page", {
179
+ pageId: z.string().describe("The Confluence page ID"),
180
+ limit: z.number().optional().describe("Max results (default 25)"),
181
+ }, async ({ pageId, limit }) => {
182
+ try {
183
+ const comments = await getConfluenceClient().listComments(pageId, limit ?? 25);
184
+ if (comments.length === 0) {
185
+ return { content: [{ type: "text", text: "No comments." }] };
186
+ }
187
+ const text = comments.map((c) => {
188
+ const author = c.history?.createdBy?.displayName ?? "Unknown";
189
+ const date = c.history?.createdDate ? new Date(c.history.createdDate).toLocaleDateString() : "";
190
+ const body = c.body?.storage?.value ?? "";
191
+ return `[${author}${date ? ` · ${date}` : ""}]\n${body}`;
192
+ }).join("\n\n");
193
+ return { content: [{ type: "text", text }] };
194
+ }
195
+ catch (err) {
196
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
197
+ }
198
+ });
199
+ server.tool("confluence_add_comment", "Add a comment to a Confluence page. IMPORTANT: Ask the user for confirmation before calling this tool.", {
200
+ pageId: z.string().describe("The Confluence page ID"),
201
+ text: z.string().describe("Comment text (plain text or Confluence storage format XHTML)"),
202
+ }, async ({ pageId, text }) => {
203
+ try {
204
+ const body = text.trimStart().startsWith("<") ? text : `<p>${text}</p>`;
205
+ await getConfluenceClient().addComment(pageId, body);
206
+ return { content: [{ type: "text", text: "✓ Comment added." }] };
207
+ }
208
+ catch (err) {
209
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
210
+ }
211
+ });
212
+ server.tool("confluence_list_labels", "List labels on a Confluence page", { pageId: z.string().describe("The Confluence page ID") }, async ({ pageId }) => {
213
+ try {
214
+ const labels = await getConfluenceClient().listLabels(pageId);
215
+ if (labels.length === 0) {
216
+ return { content: [{ type: "text", text: "No labels." }] };
217
+ }
218
+ return { content: [{ type: "text", text: labels.map((l) => l.name).join(", ") }] };
219
+ }
220
+ catch (err) {
221
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
222
+ }
223
+ });
224
+ server.tool("confluence_add_labels", "Add labels to a Confluence page. IMPORTANT: Ask the user for confirmation before calling this tool.", {
225
+ pageId: z.string().describe("The Confluence page ID"),
226
+ labels: z.array(z.string()).describe("Label names to add"),
227
+ }, async ({ pageId, labels }) => {
228
+ try {
229
+ const result = await getConfluenceClient().addLabels(pageId, labels);
230
+ const text = `✓ Added labels: ${result.map((l) => l.name).join(", ")}`;
231
+ return { content: [{ type: "text", text }] };
232
+ }
233
+ catch (err) {
234
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
235
+ }
236
+ });
237
+ server.tool("confluence_remove_label", "Remove a label from a Confluence page. IMPORTANT: Ask the user for confirmation before calling this tool.", {
238
+ pageId: z.string().describe("The Confluence page ID"),
239
+ label: z.string().describe("Label name to remove"),
240
+ }, async ({ pageId, label }) => {
241
+ try {
242
+ await getConfluenceClient().removeLabel(pageId, label);
243
+ return { content: [{ type: "text", text: `✓ Removed label "${label}"` }] };
244
+ }
245
+ catch (err) {
246
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
247
+ }
248
+ });
249
+ server.tool("confluence_list_child_pages", "List child pages of a Confluence page", {
250
+ pageId: z.string().describe("The parent page ID"),
251
+ limit: z.number().optional().describe("Max results (default 25)"),
252
+ }, async ({ pageId, limit }) => {
253
+ try {
254
+ const pages = await getConfluenceClient().listChildPages(pageId, limit ?? 25);
255
+ if (pages.length === 0) {
256
+ return { content: [{ type: "text", text: "No child pages found." }] };
257
+ }
258
+ const text = pages
259
+ .map((p) => `${p.title} (id: ${p.id}, v${p.version?.number ?? "?"})`)
260
+ .join("\n");
261
+ return { content: [{ type: "text", text }] };
262
+ }
263
+ catch (err) {
264
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
265
+ }
266
+ });
267
+ server.tool("confluence_upload_attachment", "Upload a file as an attachment to a Confluence page. IMPORTANT: Ask the user for confirmation before calling this tool.", {
268
+ pageId: z.string().describe("The page ID to attach the file to"),
269
+ filePath: z.string().describe("Absolute path to the file to upload"),
270
+ comment: z.string().optional().describe("Optional comment for the attachment"),
271
+ }, async ({ pageId, filePath, comment }) => {
272
+ try {
273
+ const att = await getConfluenceClient().uploadAttachment({ pageId, filePath, comment });
274
+ const baseUrl = process.env.ATLASSIAN_URL ?? process.env.CONFLUENCE_URL?.replace(/\/wiki\/?$/, "") ?? "";
275
+ const downloadUrl = att._links?.download ? `${baseUrl}/wiki${att._links.download}` : "";
276
+ const text = [
277
+ `✓ Attachment uploaded successfully.`,
278
+ ` File: ${att.title}`,
279
+ ` ID: ${att.id}`,
280
+ ` Type: ${att.mediaType}`,
281
+ ...(downloadUrl ? [` Download: ${downloadUrl}`] : []),
282
+ ].join("\n");
283
+ return { content: [{ type: "text", text }] };
284
+ }
285
+ catch (err) {
286
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
287
+ }
288
+ });
289
+ server.tool("confluence_list_attachments", "List all attachments on a Confluence page", { pageId: z.string().describe("The Confluence page ID") }, async ({ pageId }) => {
290
+ try {
291
+ const attachments = await getConfluenceClient().listAttachments(pageId);
292
+ if (attachments.length === 0) {
293
+ return { content: [{ type: "text", text: "No attachments found." }] };
294
+ }
295
+ const baseUrl = process.env.ATLASSIAN_URL ?? process.env.CONFLUENCE_URL?.replace(/\/wiki\/?$/, "") ?? "";
296
+ const text = attachments
297
+ .map((a) => {
298
+ const downloadUrl = a._links?.download ? `${baseUrl}/wiki${a._links.download}` : "";
299
+ return [
300
+ `${a.title} (id: ${a.id}, ${a.mediaType}${a.fileSize ? `, ${a.fileSize} bytes` : ""})`,
301
+ ...(downloadUrl ? [` ${downloadUrl}`] : []),
302
+ ].join("\n");
303
+ })
304
+ .join("\n");
305
+ return { content: [{ type: "text", text }] };
306
+ }
307
+ catch (err) {
308
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
309
+ }
310
+ });
178
311
  // ── Jira tools ─────────────────────────────────────────────────────
179
312
  server.tool("jira_auth", "Verify the Jira connection and list accessible projects", {}, async () => {
180
313
  try {
@@ -261,11 +394,12 @@ server.tool("jira_create_issue", "Create a new Jira issue. IMPORTANT: Ask the us
261
394
  description: z.string().optional().describe("Issue description (plain text)"),
262
395
  priority: z.string().optional().describe("Priority (Highest, High, Medium, Low, Lowest)"),
263
396
  labels: z.array(z.string()).optional().describe("Labels to apply"),
264
- }, async ({ projectKey, issueType, summary, description, priority, labels }) => {
397
+ parentKey: z.string().optional().describe("Parent issue key for creating a subtask (e.g. 'PROJ-10')"),
398
+ }, async ({ projectKey, issueType, summary, description, priority, labels, parentKey }) => {
265
399
  try {
266
400
  const client = getJiraClient();
267
401
  const issue = await client.createIssue({
268
- projectKey, issueType, summary, description, priority, labels,
402
+ projectKey, issueType, summary, description, priority, labels, parentKey,
269
403
  });
270
404
  const baseUrl = process.env.ATLASSIAN_URL ?? process.env.CONFLUENCE_URL?.replace(/\/wiki\/?$/, "") ?? "";
271
405
  const text = [
@@ -354,6 +488,164 @@ server.tool("jira_delete_issue", "Delete a Jira issue. IMPORTANT: Ask the user f
354
488
  return { content: [{ type: "text", text: formatError(err) }], isError: true };
355
489
  }
356
490
  });
491
+ server.tool("jira_list_worklogs", "List work log entries on a Jira issue", { issueKey: z.string().describe("The issue key (e.g. 'PROJ-123')") }, async ({ issueKey }) => {
492
+ try {
493
+ const worklogs = await getJiraClient().listWorklogs(issueKey);
494
+ if (worklogs.length === 0) {
495
+ return { content: [{ type: "text", text: "No work logged." }] };
496
+ }
497
+ const text = worklogs.map((w) => {
498
+ const author = w.author?.displayName ?? "Unknown";
499
+ const date = new Date(w.started).toLocaleDateString();
500
+ return `${author} · ${date} · ${w.timeSpent}`;
501
+ }).join("\n");
502
+ return { content: [{ type: "text", text }] };
503
+ }
504
+ catch (err) {
505
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
506
+ }
507
+ });
508
+ server.tool("jira_log_work", "Log time worked on a Jira issue. IMPORTANT: Ask the user for confirmation before calling this tool.", {
509
+ issueKey: z.string().describe("The issue key (e.g. 'PROJ-123')"),
510
+ timeSpent: z.string().describe("Time spent, e.g. '2h', '30m', '1d 2h'"),
511
+ started: z.string().optional().describe("When work started (ISO datetime, defaults to now)"),
512
+ comment: z.string().optional().describe("Work description"),
513
+ }, async ({ issueKey, timeSpent, started, comment }) => {
514
+ try {
515
+ const log = await getJiraClient().addWorklog({ issueKey, timeSpent, started, comment });
516
+ const text = [
517
+ `✓ Logged ${log.timeSpent} on ${issueKey}.`,
518
+ ` Started: ${new Date(log.started).toLocaleString()}`,
519
+ ].join("\n");
520
+ return { content: [{ type: "text", text }] };
521
+ }
522
+ catch (err) {
523
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
524
+ }
525
+ });
526
+ server.tool("jira_list_link_types", "List available Jira issue link types (e.g. Blocks, Clones, Relates)", {}, async () => {
527
+ try {
528
+ const types = await getJiraClient().listIssueLinkTypes();
529
+ const text = types
530
+ .map((t) => `${t.name} — outward: "${t.outward}", inward: "${t.inward}"`)
531
+ .join("\n");
532
+ return { content: [{ type: "text", text: text || "No link types found." }] };
533
+ }
534
+ catch (err) {
535
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
536
+ }
537
+ });
538
+ server.tool("jira_list_issue_links", "List links on a Jira issue", { issueKey: z.string().describe("The issue key (e.g. 'PROJ-123')") }, async ({ issueKey }) => {
539
+ try {
540
+ const links = await getJiraClient().listIssueLinks(issueKey);
541
+ if (links.length === 0) {
542
+ return { content: [{ type: "text", text: "No issue links." }] };
543
+ }
544
+ const text = links.map((l) => {
545
+ if (l.outwardIssue) {
546
+ return `${l.type.outward}: ${l.outwardIssue.key} — ${l.outwardIssue.fields.summary} [${l.outwardIssue.fields.status.name}] (link id: ${l.id})`;
547
+ }
548
+ if (l.inwardIssue) {
549
+ return `${l.type.inward}: ${l.inwardIssue.key} — ${l.inwardIssue.fields.summary} [${l.inwardIssue.fields.status.name}] (link id: ${l.id})`;
550
+ }
551
+ return `${l.type.name} (link id: ${l.id})`;
552
+ }).join("\n");
553
+ return { content: [{ type: "text", text }] };
554
+ }
555
+ catch (err) {
556
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
557
+ }
558
+ });
559
+ server.tool("jira_link_issues", "Link two Jira issues together. IMPORTANT: Ask the user for confirmation before calling this tool.", {
560
+ issueKey: z.string().describe("The source issue key (outward side, e.g. 'PROJ-1')"),
561
+ linkType: z.string().describe("Link type name (e.g. 'Blocks', 'Clones', 'Relates to') — use jira_list_link_types to see options"),
562
+ targetIssueKey: z.string().describe("The target issue key (inward side, e.g. 'PROJ-2')"),
563
+ }, async ({ issueKey, linkType, targetIssueKey }) => {
564
+ try {
565
+ await getJiraClient().linkIssues(issueKey, linkType, targetIssueKey);
566
+ return { content: [{ type: "text", text: `✓ Linked: ${issueKey} "${linkType}" ${targetIssueKey}` }] };
567
+ }
568
+ catch (err) {
569
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
570
+ }
571
+ });
572
+ server.tool("jira_remove_issue_link", "Remove a link between two Jira issues. IMPORTANT: Ask the user for confirmation before calling this tool.", { linkId: z.string().describe("The link ID to remove (from jira_list_issue_links)") }, async ({ linkId }) => {
573
+ try {
574
+ await getJiraClient().removeIssueLink(linkId);
575
+ return { content: [{ type: "text", text: `✓ Link ${linkId} removed.` }] };
576
+ }
577
+ catch (err) {
578
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
579
+ }
580
+ });
581
+ server.tool("jira_list_comments", "List comments on a Jira issue", { issueKey: z.string().describe("The issue key (e.g. 'PROJ-123')") }, async ({ issueKey }) => {
582
+ try {
583
+ const comments = await getJiraClient().listComments(issueKey);
584
+ if (comments.length === 0) {
585
+ return { content: [{ type: "text", text: "No comments." }] };
586
+ }
587
+ const client = getJiraClient();
588
+ const text = comments.map((c) => {
589
+ const author = c.author?.displayName ?? "Unknown";
590
+ const date = new Date(c.created).toLocaleDateString();
591
+ const body = client.descriptionToText({ fields: { description: c.body } });
592
+ return `[${author} · ${date}]\n${body}`;
593
+ }).join("\n\n");
594
+ return { content: [{ type: "text", text }] };
595
+ }
596
+ catch (err) {
597
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
598
+ }
599
+ });
600
+ server.tool("jira_add_comment", "Add a comment to a Jira issue. IMPORTANT: Ask the user for confirmation before calling this tool.", {
601
+ issueKey: z.string().describe("The issue key (e.g. 'PROJ-123')"),
602
+ text: z.string().describe("Comment text (plain text)"),
603
+ }, async ({ issueKey, text }) => {
604
+ try {
605
+ await getJiraClient().addComment(issueKey, text);
606
+ return { content: [{ type: "text", text: `✓ Comment added to ${issueKey}.` }] };
607
+ }
608
+ catch (err) {
609
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
610
+ }
611
+ });
612
+ server.tool("jira_upload_attachment", "Upload a file as an attachment to a Jira issue. IMPORTANT: Ask the user for confirmation before calling this tool.", {
613
+ issueKey: z.string().describe("The issue key to attach the file to (e.g. 'PROJ-123')"),
614
+ filePath: z.string().describe("Absolute path to the file to upload"),
615
+ }, async ({ issueKey, filePath }) => {
616
+ try {
617
+ const att = await getJiraClient().uploadAttachment({ issueKey, filePath });
618
+ const baseUrl = process.env.ATLASSIAN_URL ?? process.env.CONFLUENCE_URL?.replace(/\/wiki\/?$/, "") ?? "";
619
+ const text = [
620
+ `✓ Attachment uploaded successfully.`,
621
+ ` File: ${att.filename}`,
622
+ ` ID: ${att.id}`,
623
+ ` Type: ${att.mimeType}`,
624
+ ` Size: ${att.size} bytes`,
625
+ ...(att.content ? [` Download: ${att.content}`] : []),
626
+ ` Issue: ${baseUrl}/browse/${issueKey}`,
627
+ ].join("\n");
628
+ return { content: [{ type: "text", text }] };
629
+ }
630
+ catch (err) {
631
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
632
+ }
633
+ });
634
+ server.tool("jira_list_attachments", "List all attachments on a Jira issue", { issueKey: z.string().describe("The issue key (e.g. 'PROJ-123')") }, async ({ issueKey }) => {
635
+ try {
636
+ const attachments = await getJiraClient().listAttachments(issueKey);
637
+ if (attachments.length === 0) {
638
+ return { content: [{ type: "text", text: "No attachments found." }] };
639
+ }
640
+ const text = attachments
641
+ .map((a) => `${a.filename} (id: ${a.id}, ${a.mimeType}, ${a.size} bytes)\n ${a.content}`)
642
+ .join("\n");
643
+ return { content: [{ type: "text", text }] };
644
+ }
645
+ catch (err) {
646
+ return { content: [{ type: "text", text: formatError(err) }], isError: true };
647
+ }
648
+ });
357
649
  // ── Start ──────────────────────────────────────────────────────────
358
650
  async function main() {
359
651
  const transport = new StdioServerTransport();