@aaronsb/jira-cloud-mcp 0.2.7 → 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.
package/build/index.js CHANGED
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from 'module';
2
3
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
5
  import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
6
+ import { fieldDiscovery } from './client/field-discovery.js';
5
7
  import { JiraClient } from './client/jira-client.js';
6
- import { setupBoardHandlers } from './handlers/board-handlers.js';
7
- import { setupFilterHandlers } from './handlers/filter-handlers.js';
8
- import { setupIssueHandlers } from './handlers/issue-handlers.js';
9
- import { setupProjectHandlers } from './handlers/project-handlers.js';
8
+ import { handleBoardRequest } from './handlers/board-handlers.js';
9
+ import { handleFilterRequest } from './handlers/filter-handlers.js';
10
+ import { handleIssueRequest } from './handlers/issue-handlers.js';
11
+ import { handleProjectRequest } from './handlers/project-handlers.js';
12
+ import { createQueueHandler } from './handlers/queue-handler.js';
10
13
  import { setupResourceHandlers } from './handlers/resource-handlers.js';
11
- import { setupSprintHandlers } from './handlers/sprint-handlers.js';
14
+ import { handleSprintRequest } from './handlers/sprint-handlers.js';
12
15
  import { toolSchemas } from './schemas/tool-schemas.js';
13
16
  // Jira credentials from environment variables
14
17
  const JIRA_EMAIL = process.env.JIRA_EMAIL;
@@ -17,16 +20,17 @@ const JIRA_HOST = process.env.JIRA_HOST;
17
20
  if (!JIRA_EMAIL || !JIRA_API_TOKEN || !JIRA_HOST) {
18
21
  throw new Error('Missing required Jira credentials in environment variables');
19
22
  }
23
+ const require = createRequire(import.meta.url);
24
+ const { version } = require('../package.json');
20
25
  class JiraServer {
21
26
  server;
22
27
  jiraClient;
23
28
  constructor() {
24
- // Use environment-provided name or default to 'jira-cloud'
25
29
  const serverName = process.env.MCP_SERVER_NAME || 'jira-cloud';
26
30
  console.error(`Initializing Jira MCP server: ${serverName}`);
27
31
  this.server = new Server({
28
32
  name: serverName,
29
- version: '0.1.0',
33
+ version,
30
34
  }, {
31
35
  capabilities: {
32
36
  tools: {},
@@ -39,6 +43,8 @@ class JiraServer {
39
43
  apiToken: JIRA_API_TOKEN,
40
44
  });
41
45
  this.setupHandlers();
46
+ // Start async field discovery (non-blocking)
47
+ fieldDiscovery.startAsync(this.jiraClient.v3Client);
42
48
  this.server.onerror = (error) => console.error('[MCP Error]', error);
43
49
  process.on('SIGINT', async () => {
44
50
  await this.server.close();
@@ -49,8 +55,6 @@ class JiraServer {
49
55
  // Set up required MCP protocol handlers
50
56
  this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
51
57
  tools: Object.entries(toolSchemas)
52
- // Filter out the deprecated search_jira_issues tool
53
- .filter(([key]) => key !== 'search_jira_issues')
54
58
  .map(([key, schema]) => ({
55
59
  name: key,
56
60
  description: schema.description,
@@ -74,49 +78,22 @@ class JiraServer {
74
78
  const { name } = request.params;
75
79
  console.error(`Handling tool request: ${name}`);
76
80
  try {
77
- let response;
78
- // Issue-related tools
79
- if (['manage_jira_issue'].includes(name)) {
80
- response = await setupIssueHandlers(this.server, this.jiraClient, request);
81
- }
82
- // Project-related tools
83
- else if (['manage_jira_project'].includes(name)) {
84
- response = await setupProjectHandlers(this.server, this.jiraClient, request);
85
- }
86
- // Board-related tools
87
- else if (['manage_jira_board'].includes(name)) {
88
- response = await setupBoardHandlers(this.server, this.jiraClient, request);
89
- }
90
- // Sprint-related tools
91
- else if (['manage_jira_sprint'].includes(name)) {
92
- response = await setupSprintHandlers(this.server, this.jiraClient, request);
93
- }
94
- // Filter-related tools
95
- else if (['manage_jira_filter'].includes(name)) {
96
- response = await setupFilterHandlers(this.server, this.jiraClient, request);
97
- }
98
- // Legacy search tool - redirect to filter handler with execute_jql operation
99
- else if (name === 'search_jira_issues') {
100
- console.error('Redirecting deprecated search_jira_issues to manage_jira_filter with execute_jql operation');
101
- // Transform the request to use manage_jira_filter with execute_jql operation
102
- const transformedRequest = {
103
- ...request,
104
- params: {
105
- ...request.params,
106
- name: 'manage_jira_filter',
107
- arguments: {
108
- ...request.params.arguments,
109
- operation: 'execute_jql'
110
- }
111
- }
112
- };
113
- response = await setupFilterHandlers(this.server, this.jiraClient, transformedRequest);
114
- }
115
- else {
116
- console.error(`Unknown tool requested: ${name}`);
81
+ const toolHandlers = {
82
+ manage_jira_issue: handleIssueRequest,
83
+ manage_jira_project: handleProjectRequest,
84
+ manage_jira_board: handleBoardRequest,
85
+ manage_jira_sprint: handleSprintRequest,
86
+ manage_jira_filter: handleFilterRequest,
87
+ };
88
+ const handlers = {
89
+ ...toolHandlers,
90
+ queue_jira_operations: createQueueHandler(toolHandlers, JIRA_HOST),
91
+ };
92
+ const handler = handlers[name];
93
+ if (!handler) {
117
94
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
118
95
  }
119
- // Ensure we always return a valid response
96
+ const response = await handler(this.jiraClient, request);
120
97
  if (!response) {
121
98
  throw new McpError(ErrorCode.InternalError, `No response from handler for tool: ${name}`);
122
99
  }
@@ -127,7 +104,38 @@ class JiraServer {
127
104
  if (error instanceof McpError) {
128
105
  throw error;
129
106
  }
130
- throw new McpError(ErrorCode.InternalError, 'Internal server error');
107
+ // Surface Jira permission errors with actionable guidance
108
+ const status = error?.response?.status
109
+ ?? error?.status;
110
+ const jiraMessage = error?.response?.data?.errorMessages?.[0]
111
+ ?? error?.response?.data?.message
112
+ ?? error?.message
113
+ ?? '';
114
+ if (status === 403) {
115
+ return {
116
+ content: [{
117
+ type: 'text',
118
+ text: [
119
+ `**Jira denied this operation:** ${jiraMessage || 'Insufficient permissions.'}`,
120
+ '',
121
+ 'This is controlled by your Jira project\'s permission scheme.',
122
+ 'Contact your Jira admin to request the necessary permission,',
123
+ 'or ask them to perform the operation for you.',
124
+ ].join('\n'),
125
+ }],
126
+ isError: true,
127
+ };
128
+ }
129
+ if (status === 404) {
130
+ return {
131
+ content: [{
132
+ type: 'text',
133
+ text: `**Not found:** ${jiraMessage || 'The requested resource does not exist or you do not have permission to view it.'}`,
134
+ }],
135
+ isError: true,
136
+ };
137
+ }
138
+ throw new McpError(ErrorCode.InternalError, jiraMessage || 'Internal server error');
131
139
  }
132
140
  });
133
141
  }
@@ -131,6 +131,17 @@ export function renderIssue(issue, transitions) {
131
131
  lines.push(` ... and ${issue.comments.length - 3} more comments`);
132
132
  }
133
133
  }
134
+ // Custom fields (from catalog discovery)
135
+ if (issue.customFieldValues && issue.customFieldValues.length > 0) {
136
+ lines.push('');
137
+ lines.push('## Custom Fields');
138
+ for (const cf of issue.customFieldValues) {
139
+ const displayValue = Array.isArray(cf.value)
140
+ ? cf.value.join(', ')
141
+ : String(cf.value);
142
+ lines.push(`- **${cf.name}** (${cf.type}): ${displayValue}`);
143
+ }
144
+ }
134
145
  // Available transitions
135
146
  if (transitions && transitions.length > 0) {
136
147
  lines.push('');