@aaronsb/jira-cloud-mcp 0.1.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.
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ import axios from 'axios';
3
+ /**
4
+ * Health check script for container monitoring
5
+ * Verifies:
6
+ * 1. Environment variables are set
7
+ * 2. Jira API is accessible
8
+ * 3. Required directories exist and are writable
9
+ */
10
+ export async function checkEnvironmentVariables() {
11
+ const required = ['JIRA_API_TOKEN', 'JIRA_EMAIL', 'JIRA_HOST'];
12
+ const missing = required.filter(v => !process.env[v]);
13
+ if (missing.length > 0) {
14
+ console.error(`Missing required environment variables: ${missing.join(', ')}`);
15
+ return false;
16
+ }
17
+ return true;
18
+ }
19
+ export async function checkJiraAccess() {
20
+ const baseURL = `https://${process.env.JIRA_HOST}/rest/api/3`;
21
+ const auth = {
22
+ username: process.env.JIRA_EMAIL,
23
+ password: process.env.JIRA_API_TOKEN
24
+ };
25
+ try {
26
+ await axios.get(`${baseURL}/myself`, { auth });
27
+ return true;
28
+ }
29
+ catch (error) {
30
+ console.error('Failed to access Jira API:', error);
31
+ return false;
32
+ }
33
+ }
34
+ export async function checkDirectories() {
35
+ const fs = await import('fs/promises');
36
+ const directories = ['/app/config', '/app/logs'];
37
+ try {
38
+ for (const dir of directories) {
39
+ await fs.access(dir, fs.constants.W_OK);
40
+ }
41
+ return true;
42
+ }
43
+ catch (error) {
44
+ console.error('Directory check failed:', error);
45
+ return false;
46
+ }
47
+ }
48
+ export async function main() {
49
+ try {
50
+ const envCheck = await checkEnvironmentVariables();
51
+ const jiraCheck = await checkJiraAccess();
52
+ const dirCheck = await checkDirectories();
53
+ if (envCheck && jiraCheck && dirCheck) {
54
+ console.log('Health check passed');
55
+ process.exit(0);
56
+ }
57
+ else {
58
+ console.error('Health check failed');
59
+ process.exit(1);
60
+ }
61
+ }
62
+ catch (error) {
63
+ console.error('Health check error:', error);
64
+ process.exit(1);
65
+ }
66
+ }
67
+ main();
package/build/index.js ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ 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';
10
+ import { setupResourceHandlers } from './handlers/resource-handlers.js';
11
+ import { setupSprintHandlers } from './handlers/sprint-handlers.js';
12
+ import { toolSchemas } from './schemas/tool-schemas.js';
13
+ // Jira credentials from environment variables
14
+ const JIRA_EMAIL = process.env.JIRA_EMAIL;
15
+ const JIRA_API_TOKEN = process.env.JIRA_API_TOKEN;
16
+ const JIRA_HOST = process.env.JIRA_HOST;
17
+ if (!JIRA_EMAIL || !JIRA_API_TOKEN || !JIRA_HOST) {
18
+ throw new Error('Missing required Jira credentials in environment variables');
19
+ }
20
+ class JiraServer {
21
+ server;
22
+ jiraClient;
23
+ constructor() {
24
+ // Use environment-provided name or default to 'jira-cloud'
25
+ const serverName = process.env.MCP_SERVER_NAME || 'jira-cloud';
26
+ console.error(`Initializing Jira MCP server: ${serverName}`);
27
+ this.server = new Server({
28
+ name: serverName,
29
+ version: '0.1.0',
30
+ }, {
31
+ capabilities: {
32
+ tools: {},
33
+ resources: {},
34
+ },
35
+ });
36
+ this.jiraClient = new JiraClient({
37
+ host: JIRA_HOST,
38
+ email: JIRA_EMAIL,
39
+ apiToken: JIRA_API_TOKEN,
40
+ });
41
+ this.setupHandlers();
42
+ this.server.onerror = (error) => console.error('[MCP Error]', error);
43
+ process.on('SIGINT', async () => {
44
+ await this.server.close();
45
+ process.exit(0);
46
+ });
47
+ }
48
+ setupHandlers() {
49
+ // Set up required MCP protocol handlers
50
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
51
+ tools: Object.entries(toolSchemas)
52
+ // Filter out the deprecated search_jira_issues tool
53
+ .filter(([key]) => key !== 'search_jira_issues')
54
+ .map(([key, schema]) => ({
55
+ name: key,
56
+ description: schema.description,
57
+ inputSchema: {
58
+ type: 'object',
59
+ properties: schema.inputSchema.properties,
60
+ ...(('required' in schema.inputSchema) ? { required: schema.inputSchema.required } : {}),
61
+ },
62
+ })),
63
+ }));
64
+ // Set up resource handlers
65
+ const resourceHandlers = setupResourceHandlers(this.jiraClient);
66
+ this.server.setRequestHandler(ListResourcesRequestSchema, resourceHandlers.listResources);
67
+ this.server.setRequestHandler(ListResourceTemplatesRequestSchema, resourceHandlers.listResourceTemplates);
68
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
69
+ return resourceHandlers.readResource(request.params.uri);
70
+ });
71
+ // Set up tool handlers
72
+ this.server.setRequestHandler(CallToolRequestSchema, async (request, _extra) => {
73
+ console.error('Received request:', JSON.stringify(request, null, 2));
74
+ const { name } = request.params;
75
+ console.error(`Handling tool request: ${name}`);
76
+ 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}`);
117
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
118
+ }
119
+ // Ensure we always return a valid response
120
+ if (!response) {
121
+ throw new McpError(ErrorCode.InternalError, `No response from handler for tool: ${name}`);
122
+ }
123
+ return response;
124
+ }
125
+ catch (error) {
126
+ console.error('Error handling request:', error);
127
+ if (error instanceof McpError) {
128
+ throw error;
129
+ }
130
+ throw new McpError(ErrorCode.InternalError, 'Internal server error');
131
+ }
132
+ });
133
+ }
134
+ async run() {
135
+ const transport = new StdioServerTransport();
136
+ await this.server.connect(transport);
137
+ console.error('Jira MCP server running on stdio');
138
+ }
139
+ }
140
+ const server = new JiraServer();
141
+ server.run().catch(console.error);
@@ -0,0 +1,187 @@
1
+ import { z } from 'zod';
2
+ // Sprint Management Schemas
3
+ export const CreateJiraSprintSchema = z.object({
4
+ method: z.literal('tools/call'),
5
+ params: z.object({
6
+ name: z.literal('create_jira_sprint'),
7
+ arguments: z.object({
8
+ boardId: z.number(),
9
+ name: z.string(),
10
+ startDate: z.string().optional(),
11
+ endDate: z.string().optional(),
12
+ goal: z.string().optional(),
13
+ }),
14
+ }),
15
+ });
16
+ export const GetJiraSprintSchema = z.object({
17
+ method: z.literal('tools/call'),
18
+ params: z.object({
19
+ name: z.literal('get_jira_sprint'),
20
+ arguments: z.object({
21
+ sprintId: z.number(),
22
+ expand: z.array(z.string()).optional(),
23
+ }),
24
+ }),
25
+ });
26
+ export const ListJiraSprintsSchema = z.object({
27
+ method: z.literal('tools/call'),
28
+ params: z.object({
29
+ name: z.literal('list_jira_sprints'),
30
+ arguments: z.object({
31
+ boardId: z.number(),
32
+ state: z.enum(['future', 'active', 'closed']).optional(),
33
+ startAt: z.number().optional(),
34
+ maxResults: z.number().optional(),
35
+ expand: z.array(z.string()).optional(),
36
+ }),
37
+ }),
38
+ });
39
+ export const UpdateJiraSprintSchema = z.object({
40
+ method: z.literal('tools/call'),
41
+ params: z.object({
42
+ name: z.literal('update_jira_sprint'),
43
+ arguments: z.object({
44
+ sprintId: z.number(),
45
+ name: z.string().optional(),
46
+ goal: z.string().optional(),
47
+ startDate: z.string().optional(),
48
+ endDate: z.string().optional(),
49
+ state: z.enum(['future', 'active', 'closed']).optional(),
50
+ }),
51
+ }),
52
+ });
53
+ export const DeleteJiraSprintSchema = z.object({
54
+ method: z.literal('tools/call'),
55
+ params: z.object({
56
+ name: z.literal('delete_jira_sprint'),
57
+ arguments: z.object({
58
+ sprintId: z.number(),
59
+ }),
60
+ }),
61
+ });
62
+ export const UpdateSprintIssuesSchema = z.object({
63
+ method: z.literal('tools/call'),
64
+ params: z.object({
65
+ name: z.literal('update_sprint_issues'),
66
+ arguments: z.object({
67
+ sprintId: z.number(),
68
+ add: z.array(z.string()).optional(),
69
+ remove: z.array(z.string()).optional(),
70
+ }),
71
+ }),
72
+ });
73
+ // Consolidated API request schemas
74
+ export const GetJiraIssueSchema = z.object({
75
+ method: z.literal('tools/call'),
76
+ params: z.object({
77
+ name: z.literal('get_jira_issue'),
78
+ arguments: z.object({
79
+ issueKey: z.string(),
80
+ expand: z.array(z.string()).optional(),
81
+ }),
82
+ }),
83
+ });
84
+ export const GetJiraProjectSchema = z.object({
85
+ method: z.literal('tools/call'),
86
+ params: z.object({
87
+ name: z.literal('get_jira_project'),
88
+ arguments: z.object({
89
+ projectKey: z.string(),
90
+ expand: z.array(z.string()).optional(),
91
+ include_status_counts: z.boolean().optional(),
92
+ }),
93
+ }),
94
+ });
95
+ export const GetJiraBoardSchema = z.object({
96
+ method: z.literal('tools/call'),
97
+ params: z.object({
98
+ name: z.literal('get_jira_board'),
99
+ arguments: z.object({
100
+ boardId: z.number(),
101
+ expand: z.array(z.string()).optional(),
102
+ }),
103
+ }),
104
+ });
105
+ export const SearchJiraIssuesSchema = z.object({
106
+ method: z.literal('tools/call'),
107
+ params: z.object({
108
+ name: z.literal('search_jira_issues'),
109
+ arguments: z.object({
110
+ jql: z.string(),
111
+ startAt: z.number().optional(),
112
+ maxResults: z.number().optional(),
113
+ expand: z.array(z.string()).optional(),
114
+ }),
115
+ }),
116
+ });
117
+ export const ListJiraProjectsSchema = z.object({
118
+ method: z.literal('tools/call'),
119
+ params: z.object({
120
+ name: z.literal('list_jira_projects'),
121
+ arguments: z.object({
122
+ include_status_counts: z.boolean().optional(),
123
+ }),
124
+ }),
125
+ });
126
+ export const ListJiraBoardsSchema = z.object({
127
+ method: z.literal('tools/call'),
128
+ params: z.object({
129
+ name: z.literal('list_jira_boards'),
130
+ arguments: z.object({
131
+ include_sprints: z.boolean().optional(),
132
+ }),
133
+ }),
134
+ });
135
+ export const CreateJiraIssueSchema = z.object({
136
+ method: z.literal('tools/call'),
137
+ params: z.object({
138
+ name: z.literal('create_jira_issue'),
139
+ arguments: z.object({
140
+ projectKey: z.string(),
141
+ summary: z.string(),
142
+ description: z.string().optional(),
143
+ issueType: z.string(),
144
+ priority: z.string().optional(),
145
+ assignee: z.string().optional(),
146
+ labels: z.array(z.string()).optional(),
147
+ customFields: z.record(z.any()).optional(),
148
+ }),
149
+ }),
150
+ });
151
+ export const UpdateJiraIssueSchema = z.object({
152
+ method: z.literal('tools/call'),
153
+ params: z.object({
154
+ name: z.literal('update_jira_issue'),
155
+ arguments: z.object({
156
+ issueKey: z.string(),
157
+ summary: z.string().optional(),
158
+ description: z.string().optional(),
159
+ parent: z.union([z.string(), z.null()]).optional(),
160
+ assignee: z.string().optional(),
161
+ priority: z.string().optional(),
162
+ labels: z.array(z.string()).optional(),
163
+ customFields: z.record(z.any()).optional(),
164
+ }),
165
+ }),
166
+ });
167
+ export const TransitionJiraIssueSchema = z.object({
168
+ method: z.literal('tools/call'),
169
+ params: z.object({
170
+ name: z.literal('transition_jira_issue'),
171
+ arguments: z.object({
172
+ issueKey: z.string(),
173
+ transitionId: z.string(),
174
+ comment: z.string().optional(),
175
+ }),
176
+ }),
177
+ });
178
+ export const AddJiraCommentSchema = z.object({
179
+ method: z.literal('tools/call'),
180
+ params: z.object({
181
+ name: z.literal('add_jira_comment'),
182
+ arguments: z.object({
183
+ issueKey: z.string(),
184
+ body: z.string(),
185
+ }),
186
+ }),
187
+ });