@mcpio/jira 2.2.1 → 2.2.2

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 (2) hide show
  1. package/dist/index.js +339 -441
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -45,8 +45,8 @@ function validateProjectKey(key) {
45
45
  if (!key || typeof key !== 'string') {
46
46
  throw new Error('Invalid project key: must be a string');
47
47
  }
48
- if (!/^[A-Z][A-Z0-9_]{1,9}$/.test(key)) {
49
- throw new Error(`Invalid project key format: ${key}. Expected 2-10 uppercase alphanumeric characters`);
48
+ if (!/^[A-Z][A-Z0-9_]{0,9}$/.test(key)) {
49
+ throw new Error(`Invalid project key format: ${key}. Expected 1-10 uppercase alphanumeric characters`);
50
50
  }
51
51
  return key;
52
52
  }
@@ -69,16 +69,11 @@ function sanitizeString(str, maxLength = 1000, fieldName = 'input') {
69
69
  return str.trim();
70
70
  }
71
71
  function validateSafeParam(str, fieldName, maxLength = 100) {
72
- if (!str || typeof str !== 'string') {
73
- throw new Error(`Invalid ${fieldName}: must be a non-empty string`);
74
- }
75
- if (str.length > maxLength) {
76
- throw new Error(`${fieldName} exceeds maximum length of ${maxLength} characters`);
77
- }
78
- if (/[\/\\]/.test(str)) {
72
+ const value = sanitizeString(str, maxLength, fieldName);
73
+ if (/[\/\\]/.test(value)) {
79
74
  throw new Error(`Invalid ${fieldName}: contains unsafe characters`);
80
75
  }
81
- return str.trim();
76
+ return value;
82
77
  }
83
78
  function validateMaxResults(maxResults) {
84
79
  if (typeof maxResults !== 'number' || !Number.isInteger(maxResults) || maxResults < 1) {
@@ -205,34 +200,19 @@ function parseInlineContent(text) {
205
200
  if (lastIndex < text.length) {
206
201
  parts.push({ type: 'text', text: text.substring(lastIndex) });
207
202
  }
208
- if (parts.length > 0)
209
- return parts;
210
- return text ? [{ type: 'text', text }] : [];
203
+ return parts;
211
204
  }
212
- function addBulletItem(nodes, content) {
205
+ function addListItem(nodes, content, listType) {
213
206
  const listItem = {
214
207
  type: 'listItem',
215
208
  content: [{ type: 'paragraph', content }]
216
209
  };
217
210
  const lastNode = nodes[nodes.length - 1];
218
- if (lastNode && lastNode.type === 'bulletList') {
211
+ if (lastNode && lastNode.type === listType) {
219
212
  lastNode.content.push(listItem);
220
213
  }
221
214
  else {
222
- nodes.push({ type: 'bulletList', content: [listItem] });
223
- }
224
- }
225
- function addOrderedItem(nodes, content) {
226
- const listItem = {
227
- type: 'listItem',
228
- content: [{ type: 'paragraph', content }]
229
- };
230
- const lastNode = nodes[nodes.length - 1];
231
- if (lastNode && lastNode.type === 'orderedList') {
232
- lastNode.content.push(listItem);
233
- }
234
- else {
235
- nodes.push({ type: 'orderedList', content: [listItem] });
215
+ nodes.push({ type: listType, content: [listItem] });
236
216
  }
237
217
  }
238
218
  function createADFDocument(content) {
@@ -266,10 +246,10 @@ function createADFDocument(content) {
266
246
  });
267
247
  }
268
248
  else if (line.startsWith('* ') || line.startsWith('- ')) {
269
- addBulletItem(nodes, parseInlineContent(line.substring(2)));
249
+ addListItem(nodes, parseInlineContent(line.substring(2)), 'bulletList');
270
250
  }
271
251
  else if (/^\d+\.\s+/.test(line)) {
272
- addOrderedItem(nodes, parseInlineContent(line.replace(/^\d+\.\s+/, '')));
252
+ addListItem(nodes, parseInlineContent(line.replace(/^\d+\.\s+/, '')), 'orderedList');
273
253
  }
274
254
  else if (line.startsWith('> ')) {
275
255
  const text = line.substring(2);
@@ -695,419 +675,337 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
695
675
  ],
696
676
  };
697
677
  });
678
+ async function handleCreateIssue(a) {
679
+ const { summary, description, issueType = 'Task', priority = 'Medium', labels = [], storyPoints } = a;
680
+ const projectKey = a.projectKey ? validateProjectKey(a.projectKey) : JIRA_PROJECT_KEY;
681
+ validateSafeParam(issueType, 'issueType');
682
+ validateSafeParam(priority, 'priority');
683
+ const validatedLabels = validateLabels(labels);
684
+ const issueData = {
685
+ fields: {
686
+ project: { key: projectKey },
687
+ summary: sanitizeString(summary, 500, 'summary'),
688
+ description: createADFDocument(description),
689
+ issuetype: { name: issueType },
690
+ priority: { name: priority },
691
+ labels: validatedLabels,
692
+ },
693
+ };
694
+ if (storyPoints !== undefined && storyPoints !== null) {
695
+ issueData.fields[STORY_POINTS_FIELD] = validateStoryPoints(storyPoints);
696
+ }
697
+ const response = await jiraApi.post('/issue', issueData);
698
+ return createSuccessResponse({
699
+ success: true,
700
+ key: response.data.key,
701
+ id: response.data.id,
702
+ url: createIssueUrl(response.data.key),
703
+ });
704
+ }
705
+ async function handleGetIssue(a) {
706
+ validateIssueKey(a.issueKey);
707
+ const response = await jiraApi.get(`/issue/${a.issueKey}`);
708
+ const f = response.data.fields;
709
+ return createSuccessResponse({
710
+ key: response.data.key,
711
+ summary: f.summary,
712
+ description: adfToText(f.description),
713
+ status: f.status?.name,
714
+ assignee: f.assignee ? { displayName: f.assignee.displayName, accountId: f.assignee.accountId } : null,
715
+ reporter: f.reporter?.displayName,
716
+ priority: f.priority?.name,
717
+ issueType: f.issuetype?.name,
718
+ labels: f.labels || [],
719
+ storyPoints: f[STORY_POINTS_FIELD],
720
+ parent: f.parent?.key,
721
+ created: f.created,
722
+ updated: f.updated,
723
+ url: createIssueUrl(response.data.key),
724
+ });
725
+ }
726
+ async function handleSearchIssues(a) {
727
+ const { jql, maxResults = 50 } = a;
728
+ validateJQL(jql);
729
+ const validatedMaxResults = validateMaxResults(maxResults);
730
+ const response = await jiraApi.get('/search/jql', {
731
+ params: {
732
+ jql,
733
+ maxResults: validatedMaxResults,
734
+ fields: 'summary,status,assignee,priority,created,updated,issuetype,parent,labels',
735
+ },
736
+ });
737
+ return createSuccessResponse({
738
+ total: response.data.total,
739
+ issues: response.data.issues.map((issue) => ({
740
+ key: issue.key,
741
+ summary: issue.fields.summary,
742
+ status: issue.fields.status?.name,
743
+ assignee: issue.fields.assignee ? { displayName: issue.fields.assignee.displayName, accountId: issue.fields.assignee.accountId } : null,
744
+ priority: issue.fields.priority?.name,
745
+ issueType: issue.fields.issuetype?.name,
746
+ labels: issue.fields.labels || [],
747
+ parent: issue.fields.parent?.key,
748
+ url: createIssueUrl(issue.key),
749
+ })),
750
+ });
751
+ }
752
+ async function handleUpdateIssue(a) {
753
+ const { issueKey, summary, description, status } = a;
754
+ validateIssueKey(issueKey);
755
+ const updateData = { fields: {} };
756
+ let hasFieldUpdates = false;
757
+ if (summary) {
758
+ updateData.fields.summary = sanitizeString(summary, 500, 'summary');
759
+ hasFieldUpdates = true;
760
+ }
761
+ if (description) {
762
+ updateData.fields.description = createADFDocument(description);
763
+ hasFieldUpdates = true;
764
+ }
765
+ if (hasFieldUpdates) {
766
+ await jiraApi.put(`/issue/${issueKey}`, updateData);
767
+ }
768
+ const warnings = [];
769
+ if (status) {
770
+ const transitions = await jiraApi.get(`/issue/${issueKey}/transitions`);
771
+ const transition = transitions.data.transitions.find((t) => t.name === status);
772
+ if (transition) {
773
+ await jiraApi.post(`/issue/${issueKey}/transitions`, {
774
+ transition: { id: transition.id },
775
+ });
776
+ }
777
+ else {
778
+ const available = transitions.data.transitions.map((t) => t.name).join(', ');
779
+ warnings.push(`Transition "${status}" not found. Available transitions: ${available}`);
780
+ }
781
+ }
782
+ if (!hasFieldUpdates && !status) {
783
+ return createSuccessResponse({ success: false, message: `No updates provided for ${issueKey}` });
784
+ }
785
+ const result = {
786
+ success: warnings.length === 0,
787
+ message: `Issue ${issueKey} updated${warnings.length > 0 ? ' with warnings' : ' successfully'}`,
788
+ url: createIssueUrl(issueKey),
789
+ };
790
+ if (warnings.length > 0) {
791
+ result.warnings = warnings;
792
+ }
793
+ return createSuccessResponse(result);
794
+ }
795
+ async function handleAddComment(a) {
796
+ validateIssueKey(a.issueKey);
797
+ await jiraApi.post(`/issue/${a.issueKey}/comment`, { body: createADFDocument(a.comment) });
798
+ return createSuccessResponse({ success: true, message: `Comment added to ${a.issueKey}` });
799
+ }
800
+ async function handleLinkIssues(a) {
801
+ const { inwardIssue, outwardIssue, linkType = 'Relates' } = a;
802
+ validateIssueKey(inwardIssue);
803
+ validateIssueKey(outwardIssue);
804
+ validateSafeParam(linkType, 'linkType');
805
+ try {
806
+ await jiraApi.post('/issueLink', {
807
+ type: { name: linkType },
808
+ inwardIssue: { key: inwardIssue },
809
+ outwardIssue: { key: outwardIssue },
810
+ });
811
+ return createSuccessResponse({ success: true, message: `Linked ${inwardIssue} to ${outwardIssue} with type "${linkType}"` });
812
+ }
813
+ catch (linkError) {
814
+ const axiosErr = linkError;
815
+ if (axiosErr.response?.status === 400 && axiosErr.response?.data?.errorMessages?.includes('link already exists')) {
816
+ return createSuccessResponse({ success: true, message: `Link between ${inwardIssue} and ${outwardIssue} already exists`, alreadyLinked: true });
817
+ }
818
+ throw linkError;
819
+ }
820
+ }
821
+ async function handleGetProjectInfo(a) {
822
+ const projectKey = a.projectKey ?? JIRA_PROJECT_KEY;
823
+ validateProjectKey(projectKey);
824
+ const response = await jiraApi.get(`/project/${projectKey}`);
825
+ return createSuccessResponse({
826
+ key: response.data.key,
827
+ name: response.data.name,
828
+ description: response.data.description,
829
+ lead: response.data.lead?.displayName,
830
+ url: response.data.url,
831
+ });
832
+ }
833
+ async function handleDeleteIssue(a) {
834
+ validateIssueKey(a.issueKey);
835
+ await jiraApi.delete(`/issue/${a.issueKey}`);
836
+ return createSuccessResponse({ success: true, message: `Issue ${a.issueKey} deleted successfully` });
837
+ }
838
+ async function handleCreateSubtask(a) {
839
+ const { parentKey, summary, description, priority = 'Medium' } = a;
840
+ validateIssueKey(parentKey);
841
+ validateSafeParam(priority, 'priority');
842
+ const projectKey = a.projectKey ? validateProjectKey(a.projectKey) : JIRA_PROJECT_KEY;
843
+ const response = await jiraApi.post('/issue', {
844
+ fields: {
845
+ project: { key: projectKey },
846
+ summary: sanitizeString(summary, 500, 'summary'),
847
+ description: createADFDocument(description),
848
+ issuetype: { name: 'Subtask' },
849
+ priority: { name: priority },
850
+ parent: { key: parentKey },
851
+ },
852
+ });
853
+ return createSuccessResponse({ success: true, key: response.data.key, id: response.data.id, parent: parentKey, url: createIssueUrl(response.data.key) });
854
+ }
855
+ async function handleAssignIssue(a) {
856
+ validateIssueKey(a.issueKey);
857
+ await jiraApi.put(`/issue/${a.issueKey}/assignee`, { accountId: a.accountId !== undefined ? a.accountId : null });
858
+ return createSuccessResponse({
859
+ success: true,
860
+ message: a.accountId ? `Issue ${a.issueKey} assigned to ${a.accountId}` : `Issue ${a.issueKey} unassigned`,
861
+ url: createIssueUrl(a.issueKey),
862
+ });
863
+ }
864
+ async function handleListTransitions(a) {
865
+ validateIssueKey(a.issueKey);
866
+ const response = await jiraApi.get(`/issue/${a.issueKey}/transitions`);
867
+ return createSuccessResponse({
868
+ issueKey: a.issueKey,
869
+ transitions: response.data.transitions.map((t) => ({
870
+ id: t.id,
871
+ name: t.name,
872
+ to: { id: t.to.id, name: t.to.name, category: t.to.statusCategory?.name },
873
+ })),
874
+ });
875
+ }
876
+ async function handleAddWorklog(a) {
877
+ const { issueKey, timeSpent, comment, started } = a;
878
+ validateIssueKey(issueKey);
879
+ sanitizeString(timeSpent, 50, 'timeSpent');
880
+ const worklogData = { timeSpent };
881
+ if (comment)
882
+ worklogData.comment = createADFDocument(comment);
883
+ if (started)
884
+ worklogData.started = started;
885
+ const response = await jiraApi.post(`/issue/${issueKey}/worklog`, worklogData);
886
+ return createSuccessResponse({ success: true, id: response.data.id, issueKey, timeSpent: response.data.timeSpent, author: response.data.author?.displayName });
887
+ }
888
+ async function handleGetComments(a) {
889
+ const { issueKey, maxResults = 50, orderBy = '-created' } = a;
890
+ validateIssueKey(issueKey);
891
+ const validatedMaxResults = validateMaxResults(maxResults);
892
+ const response = await jiraApi.get(`/issue/${issueKey}/comment`, { params: { maxResults: validatedMaxResults, orderBy } });
893
+ return createSuccessResponse({
894
+ issueKey,
895
+ total: response.data.total,
896
+ comments: response.data.comments.map((c) => ({
897
+ id: c.id,
898
+ author: c.author?.displayName,
899
+ body: adfToText(c.body),
900
+ created: c.created,
901
+ updated: c.updated,
902
+ })),
903
+ });
904
+ }
905
+ async function handleGetWorklogs(a) {
906
+ validateIssueKey(a.issueKey);
907
+ const response = await jiraApi.get(`/issue/${a.issueKey}/worklog`);
908
+ return createSuccessResponse({
909
+ issueKey: a.issueKey,
910
+ total: response.data.total,
911
+ worklogs: response.data.worklogs.map((w) => ({
912
+ id: w.id,
913
+ author: w.author?.displayName,
914
+ timeSpent: w.timeSpent,
915
+ timeSpentSeconds: w.timeSpentSeconds,
916
+ started: w.started,
917
+ comment: adfToText(w.comment),
918
+ })),
919
+ });
920
+ }
921
+ async function handleListProjects(a) {
922
+ const { maxResults = 50, query } = a;
923
+ const validatedMaxResults = validateMaxResults(maxResults);
924
+ const params = { maxResults: validatedMaxResults };
925
+ if (query)
926
+ params.query = sanitizeString(query, 200, 'query');
927
+ const response = await jiraApi.get('/project/search', { params });
928
+ return createSuccessResponse({
929
+ total: response.data.total,
930
+ projects: response.data.values.map((p) => ({ key: p.key, name: p.name, projectTypeKey: p.projectTypeKey, style: p.style, lead: p.lead?.displayName })),
931
+ });
932
+ }
933
+ async function handleGetProjectComponents(a) {
934
+ const projectKey = a?.projectKey ? validateProjectKey(a.projectKey) : JIRA_PROJECT_KEY;
935
+ const response = await jiraApi.get(`/project/${projectKey}/components`);
936
+ return createSuccessResponse({
937
+ projectKey,
938
+ components: response.data.map((c) => ({ id: c.id, name: c.name, description: c.description, lead: c.lead?.displayName, assigneeType: c.assigneeType })),
939
+ });
940
+ }
941
+ async function handleGetProjectVersions(a) {
942
+ const projectKey = a?.projectKey ? validateProjectKey(a.projectKey) : JIRA_PROJECT_KEY;
943
+ const response = await jiraApi.get(`/project/${projectKey}/versions`);
944
+ return createSuccessResponse({
945
+ projectKey,
946
+ versions: response.data.map((v) => ({ id: v.id, name: v.name, description: v.description, released: v.released, archived: v.archived, releaseDate: v.releaseDate, startDate: v.startDate })),
947
+ });
948
+ }
949
+ async function handleGetFields(_a) {
950
+ const response = await jiraApi.get('/field');
951
+ return createSuccessResponse({ fields: response.data.map((f) => ({ id: f.id, name: f.name, custom: f.custom, schema: f.schema })) });
952
+ }
953
+ async function handleGetIssueTypes(a) {
954
+ const projectKey = a?.projectKey ? validateProjectKey(a.projectKey) : JIRA_PROJECT_KEY;
955
+ const response = await jiraApi.get(`/issue/createmeta/${projectKey}/issuetypes`);
956
+ return createSuccessResponse({
957
+ projectKey,
958
+ issueTypes: response.data.issueTypes.map((t) => ({ id: t.id, name: t.name, subtask: t.subtask, description: t.description })),
959
+ });
960
+ }
961
+ async function handleGetPriorities(_a) {
962
+ const response = await jiraApi.get('/priority/search');
963
+ return createSuccessResponse({ priorities: response.data.values.map((p) => ({ id: p.id, name: p.name, description: p.description, iconUrl: p.iconUrl })) });
964
+ }
965
+ async function handleGetLinkTypes(_a) {
966
+ const response = await jiraApi.get('/issueLinkType');
967
+ return createSuccessResponse({ linkTypes: response.data.issueLinkTypes.map((lt) => ({ id: lt.id, name: lt.name, inward: lt.inward, outward: lt.outward })) });
968
+ }
969
+ async function handleSearchUsers(a) {
970
+ const { query, maxResults = 10 } = a;
971
+ sanitizeString(query, 200, 'query');
972
+ const validatedMaxResults = validateMaxResults(maxResults);
973
+ const response = await jiraApi.get('/user/search', { params: { query, maxResults: validatedMaxResults } });
974
+ return createSuccessResponse({
975
+ users: response.data.map((u) => ({ accountId: u.accountId, displayName: u.displayName, emailAddress: u.emailAddress, active: u.active, accountType: u.accountType })),
976
+ });
977
+ }
978
+ const toolHandlers = {
979
+ jira_create_issue: handleCreateIssue,
980
+ jira_get_issue: handleGetIssue,
981
+ jira_search_issues: handleSearchIssues,
982
+ jira_update_issue: handleUpdateIssue,
983
+ jira_add_comment: handleAddComment,
984
+ jira_link_issues: handleLinkIssues,
985
+ jira_get_project_info: handleGetProjectInfo,
986
+ jira_delete_issue: handleDeleteIssue,
987
+ jira_create_subtask: handleCreateSubtask,
988
+ jira_assign_issue: handleAssignIssue,
989
+ jira_list_transitions: handleListTransitions,
990
+ jira_add_worklog: handleAddWorklog,
991
+ jira_get_comments: handleGetComments,
992
+ jira_get_worklogs: handleGetWorklogs,
993
+ jira_list_projects: handleListProjects,
994
+ jira_get_project_components: handleGetProjectComponents,
995
+ jira_get_project_versions: handleGetProjectVersions,
996
+ jira_get_fields: handleGetFields,
997
+ jira_get_issue_types: handleGetIssueTypes,
998
+ jira_get_priorities: handleGetPriorities,
999
+ jira_get_link_types: handleGetLinkTypes,
1000
+ jira_search_users: handleSearchUsers,
1001
+ };
698
1002
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
699
1003
  const { name, arguments: args } = request.params;
700
- const a = args;
1004
+ const handler = toolHandlers[name];
1005
+ if (!handler)
1006
+ throw new Error(`Unknown tool: ${name}`);
701
1007
  try {
702
- switch (name) {
703
- case 'jira_create_issue': {
704
- const { summary, description, issueType = 'Task', priority = 'Medium', labels = [], storyPoints } = a;
705
- const projectKey = a.projectKey ? validateProjectKey(a.projectKey) : JIRA_PROJECT_KEY;
706
- validateSafeParam(issueType, 'issueType');
707
- validateSafeParam(priority, 'priority');
708
- const validatedLabels = validateLabels(labels);
709
- const issueData = {
710
- fields: {
711
- project: { key: projectKey },
712
- summary: sanitizeString(summary, 500, 'summary'),
713
- description: createADFDocument(description),
714
- issuetype: { name: issueType },
715
- priority: { name: priority },
716
- labels: validatedLabels,
717
- },
718
- };
719
- if (storyPoints !== undefined && storyPoints !== null) {
720
- issueData.fields[STORY_POINTS_FIELD] = validateStoryPoints(storyPoints);
721
- }
722
- const response = await jiraApi.post('/issue', issueData);
723
- return createSuccessResponse({
724
- success: true,
725
- key: response.data.key,
726
- id: response.data.id,
727
- url: createIssueUrl(response.data.key),
728
- });
729
- }
730
- case 'jira_get_issue': {
731
- const { issueKey } = a;
732
- validateIssueKey(issueKey);
733
- const response = await jiraApi.get(`/issue/${issueKey}`);
734
- const f = response.data.fields;
735
- return createSuccessResponse({
736
- key: response.data.key,
737
- summary: f.summary,
738
- description: adfToText(f.description),
739
- status: f.status?.name,
740
- assignee: f.assignee ? { displayName: f.assignee.displayName, accountId: f.assignee.accountId } : null,
741
- reporter: f.reporter?.displayName,
742
- priority: f.priority?.name,
743
- issueType: f.issuetype?.name,
744
- labels: f.labels || [],
745
- storyPoints: f[STORY_POINTS_FIELD],
746
- parent: f.parent?.key,
747
- created: f.created,
748
- updated: f.updated,
749
- url: createIssueUrl(response.data.key),
750
- });
751
- }
752
- case 'jira_search_issues': {
753
- const { jql, maxResults = 50 } = a;
754
- validateJQL(jql);
755
- const validatedMaxResults = validateMaxResults(maxResults);
756
- const response = await jiraApi.get('/search/jql', {
757
- params: {
758
- jql,
759
- maxResults: validatedMaxResults,
760
- fields: 'summary,status,assignee,priority,created,updated,issuetype,parent,labels',
761
- },
762
- });
763
- return createSuccessResponse({
764
- total: response.data.total,
765
- issues: response.data.issues.map((issue) => ({
766
- key: issue.key,
767
- summary: issue.fields.summary,
768
- status: issue.fields.status?.name,
769
- assignee: issue.fields.assignee ? { displayName: issue.fields.assignee.displayName, accountId: issue.fields.assignee.accountId } : null,
770
- priority: issue.fields.priority?.name,
771
- issueType: issue.fields.issuetype?.name,
772
- labels: issue.fields.labels || [],
773
- parent: issue.fields.parent?.key,
774
- url: createIssueUrl(issue.key),
775
- })),
776
- });
777
- }
778
- case 'jira_update_issue': {
779
- const { issueKey, summary, description, status } = a;
780
- validateIssueKey(issueKey);
781
- const updateData = { fields: {} };
782
- let hasFieldUpdates = false;
783
- if (summary) {
784
- updateData.fields.summary = sanitizeString(summary, 500, 'summary');
785
- hasFieldUpdates = true;
786
- }
787
- if (description) {
788
- updateData.fields.description = createADFDocument(description);
789
- hasFieldUpdates = true;
790
- }
791
- if (hasFieldUpdates) {
792
- await jiraApi.put(`/issue/${issueKey}`, updateData);
793
- }
794
- const warnings = [];
795
- if (status) {
796
- const transitions = await jiraApi.get(`/issue/${issueKey}/transitions`);
797
- const transition = transitions.data.transitions.find((t) => t.name === status);
798
- if (transition) {
799
- await jiraApi.post(`/issue/${issueKey}/transitions`, {
800
- transition: { id: transition.id },
801
- });
802
- }
803
- else {
804
- const available = transitions.data.transitions.map((t) => t.name).join(', ');
805
- warnings.push(`Transition "${status}" not found. Available transitions: ${available}`);
806
- }
807
- }
808
- if (!hasFieldUpdates && !status) {
809
- return createSuccessResponse({
810
- success: false,
811
- message: `No updates provided for ${issueKey}`,
812
- });
813
- }
814
- const result = {
815
- success: warnings.length === 0,
816
- message: `Issue ${issueKey} updated${warnings.length > 0 ? ' with warnings' : ' successfully'}`,
817
- url: createIssueUrl(issueKey),
818
- };
819
- if (warnings.length > 0) {
820
- result.warnings = warnings;
821
- }
822
- return createSuccessResponse(result);
823
- }
824
- case 'jira_add_comment': {
825
- const { issueKey, comment } = a;
826
- validateIssueKey(issueKey);
827
- await jiraApi.post(`/issue/${issueKey}/comment`, {
828
- body: createADFDocument(comment),
829
- });
830
- return createSuccessResponse({
831
- success: true,
832
- message: `Comment added to ${issueKey}`,
833
- });
834
- }
835
- case 'jira_link_issues': {
836
- const { inwardIssue, outwardIssue, linkType = 'Relates' } = a;
837
- validateIssueKey(inwardIssue);
838
- validateIssueKey(outwardIssue);
839
- validateSafeParam(linkType, 'linkType');
840
- try {
841
- await jiraApi.post('/issueLink', {
842
- type: { name: linkType },
843
- inwardIssue: { key: inwardIssue },
844
- outwardIssue: { key: outwardIssue },
845
- });
846
- return createSuccessResponse({
847
- success: true,
848
- message: `Linked ${inwardIssue} to ${outwardIssue} with type "${linkType}"`,
849
- });
850
- }
851
- catch (linkError) {
852
- const axiosErr = linkError;
853
- if (axiosErr.response?.status === 400 &&
854
- axiosErr.response?.data?.errorMessages?.includes('link already exists')) {
855
- return createSuccessResponse({
856
- success: true,
857
- message: `Link between ${inwardIssue} and ${outwardIssue} already exists`,
858
- alreadyLinked: true,
859
- });
860
- }
861
- throw linkError;
862
- }
863
- }
864
- case 'jira_get_project_info': {
865
- const { projectKey = JIRA_PROJECT_KEY } = a;
866
- validateProjectKey(projectKey);
867
- const response = await jiraApi.get(`/project/${projectKey}`);
868
- return createSuccessResponse({
869
- key: response.data.key,
870
- name: response.data.name,
871
- description: response.data.description,
872
- lead: response.data.lead?.displayName,
873
- url: response.data.url,
874
- });
875
- }
876
- case 'jira_delete_issue': {
877
- const { issueKey } = a;
878
- validateIssueKey(issueKey);
879
- await jiraApi.delete(`/issue/${issueKey}`);
880
- return createSuccessResponse({
881
- success: true,
882
- message: `Issue ${issueKey} deleted successfully`,
883
- });
884
- }
885
- case 'jira_create_subtask': {
886
- const { parentKey, summary, description, priority = 'Medium' } = a;
887
- validateIssueKey(parentKey);
888
- validateSafeParam(priority, 'priority');
889
- const projectKey = a.projectKey ? validateProjectKey(a.projectKey) : JIRA_PROJECT_KEY;
890
- const issueData = {
891
- fields: {
892
- project: { key: projectKey },
893
- summary: sanitizeString(summary, 500, 'summary'),
894
- description: createADFDocument(description),
895
- issuetype: { name: 'Subtask' },
896
- priority: { name: priority },
897
- parent: { key: parentKey },
898
- },
899
- };
900
- const response = await jiraApi.post('/issue', issueData);
901
- return createSuccessResponse({
902
- success: true,
903
- key: response.data.key,
904
- id: response.data.id,
905
- parent: parentKey,
906
- url: createIssueUrl(response.data.key),
907
- });
908
- }
909
- case 'jira_assign_issue': {
910
- const { issueKey, accountId } = a;
911
- validateIssueKey(issueKey);
912
- await jiraApi.put(`/issue/${issueKey}/assignee`, {
913
- accountId: accountId !== undefined ? accountId : null,
914
- });
915
- return createSuccessResponse({
916
- success: true,
917
- message: accountId
918
- ? `Issue ${issueKey} assigned to ${accountId}`
919
- : `Issue ${issueKey} unassigned`,
920
- url: createIssueUrl(issueKey),
921
- });
922
- }
923
- case 'jira_list_transitions': {
924
- const { issueKey } = a;
925
- validateIssueKey(issueKey);
926
- const response = await jiraApi.get(`/issue/${issueKey}/transitions`);
927
- return createSuccessResponse({
928
- issueKey,
929
- transitions: response.data.transitions.map((t) => ({
930
- id: t.id,
931
- name: t.name,
932
- to: {
933
- id: t.to.id,
934
- name: t.to.name,
935
- category: t.to.statusCategory?.name,
936
- },
937
- })),
938
- });
939
- }
940
- case 'jira_add_worklog': {
941
- const { issueKey, timeSpent, comment, started } = a;
942
- validateIssueKey(issueKey);
943
- sanitizeString(timeSpent, 50, 'timeSpent');
944
- const worklogData = { timeSpent };
945
- if (comment) {
946
- worklogData.comment = createADFDocument(comment);
947
- }
948
- if (started) {
949
- worklogData.started = started;
950
- }
951
- const response = await jiraApi.post(`/issue/${issueKey}/worklog`, worklogData);
952
- return createSuccessResponse({
953
- success: true,
954
- id: response.data.id,
955
- issueKey,
956
- timeSpent: response.data.timeSpent,
957
- author: response.data.author?.displayName,
958
- });
959
- }
960
- case 'jira_get_comments': {
961
- const { issueKey, maxResults = 50, orderBy = '-created' } = a;
962
- validateIssueKey(issueKey);
963
- const validatedMaxResults = validateMaxResults(maxResults);
964
- const response = await jiraApi.get(`/issue/${issueKey}/comment`, {
965
- params: { maxResults: validatedMaxResults, orderBy },
966
- });
967
- return createSuccessResponse({
968
- issueKey,
969
- total: response.data.total,
970
- comments: response.data.comments.map((c) => ({
971
- id: c.id,
972
- author: c.author?.displayName,
973
- body: adfToText(c.body),
974
- created: c.created,
975
- updated: c.updated,
976
- })),
977
- });
978
- }
979
- case 'jira_get_worklogs': {
980
- const { issueKey } = a;
981
- validateIssueKey(issueKey);
982
- const response = await jiraApi.get(`/issue/${issueKey}/worklog`);
983
- return createSuccessResponse({
984
- issueKey,
985
- total: response.data.total,
986
- worklogs: response.data.worklogs.map((w) => ({
987
- id: w.id,
988
- author: w.author?.displayName,
989
- timeSpent: w.timeSpent,
990
- timeSpentSeconds: w.timeSpentSeconds,
991
- started: w.started,
992
- comment: adfToText(w.comment),
993
- })),
994
- });
995
- }
996
- case 'jira_list_projects': {
997
- const { maxResults = 50, query } = a;
998
- const validatedMaxResults = validateMaxResults(maxResults);
999
- const params = { maxResults: validatedMaxResults };
1000
- if (query) {
1001
- params.query = sanitizeString(query, 200, 'query');
1002
- }
1003
- const response = await jiraApi.get('/project/search', { params });
1004
- return createSuccessResponse({
1005
- total: response.data.total,
1006
- projects: response.data.values.map((p) => ({
1007
- key: p.key,
1008
- name: p.name,
1009
- projectTypeKey: p.projectTypeKey,
1010
- style: p.style,
1011
- lead: p.lead?.displayName,
1012
- })),
1013
- });
1014
- }
1015
- case 'jira_get_project_components': {
1016
- const projectKey = a?.projectKey ? validateProjectKey(a.projectKey) : JIRA_PROJECT_KEY;
1017
- const response = await jiraApi.get(`/project/${projectKey}/components`);
1018
- return createSuccessResponse({
1019
- projectKey,
1020
- components: response.data.map((c) => ({
1021
- id: c.id,
1022
- name: c.name,
1023
- description: c.description,
1024
- lead: c.lead?.displayName,
1025
- assigneeType: c.assigneeType,
1026
- })),
1027
- });
1028
- }
1029
- case 'jira_get_project_versions': {
1030
- const projectKey = a?.projectKey ? validateProjectKey(a.projectKey) : JIRA_PROJECT_KEY;
1031
- const response = await jiraApi.get(`/project/${projectKey}/versions`);
1032
- return createSuccessResponse({
1033
- projectKey,
1034
- versions: response.data.map((v) => ({
1035
- id: v.id,
1036
- name: v.name,
1037
- description: v.description,
1038
- released: v.released,
1039
- archived: v.archived,
1040
- releaseDate: v.releaseDate,
1041
- startDate: v.startDate,
1042
- })),
1043
- });
1044
- }
1045
- case 'jira_get_fields': {
1046
- const response = await jiraApi.get('/field');
1047
- return createSuccessResponse({
1048
- fields: response.data.map((f) => ({
1049
- id: f.id,
1050
- name: f.name,
1051
- custom: f.custom,
1052
- schema: f.schema,
1053
- })),
1054
- });
1055
- }
1056
- case 'jira_get_issue_types': {
1057
- const projectKey = a?.projectKey ? validateProjectKey(a.projectKey) : JIRA_PROJECT_KEY;
1058
- const response = await jiraApi.get(`/issue/createmeta/${projectKey}/issuetypes`);
1059
- return createSuccessResponse({
1060
- projectKey,
1061
- issueTypes: response.data.issueTypes.map((t) => ({
1062
- id: t.id,
1063
- name: t.name,
1064
- subtask: t.subtask,
1065
- description: t.description,
1066
- })),
1067
- });
1068
- }
1069
- case 'jira_get_priorities': {
1070
- const response = await jiraApi.get('/priority/search');
1071
- return createSuccessResponse({
1072
- priorities: response.data.values.map((p) => ({
1073
- id: p.id,
1074
- name: p.name,
1075
- description: p.description,
1076
- iconUrl: p.iconUrl,
1077
- })),
1078
- });
1079
- }
1080
- case 'jira_get_link_types': {
1081
- const response = await jiraApi.get('/issueLinkType');
1082
- return createSuccessResponse({
1083
- linkTypes: response.data.issueLinkTypes.map((lt) => ({
1084
- id: lt.id,
1085
- name: lt.name,
1086
- inward: lt.inward,
1087
- outward: lt.outward,
1088
- })),
1089
- });
1090
- }
1091
- case 'jira_search_users': {
1092
- const { query, maxResults = 10 } = a;
1093
- sanitizeString(query, 200, 'query');
1094
- const validatedMaxResults = validateMaxResults(maxResults);
1095
- const response = await jiraApi.get('/user/search', {
1096
- params: { query, maxResults: validatedMaxResults },
1097
- });
1098
- return createSuccessResponse({
1099
- users: response.data.map((u) => ({
1100
- accountId: u.accountId,
1101
- displayName: u.displayName,
1102
- emailAddress: u.emailAddress,
1103
- active: u.active,
1104
- accountType: u.accountType,
1105
- })),
1106
- });
1107
- }
1108
- default:
1109
- throw new Error(`Unknown tool: ${name}`);
1110
- }
1008
+ return await handler(args);
1111
1009
  }
1112
1010
  catch (error) {
1113
1011
  return handleError(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcpio/jira",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "Model Context Protocol (MCP) server for Jira Cloud API v3 with automatic Markdown-to-ADF conversion",
5
5
  "author": {
6
6
  "name": "Volodymyr Press",