mcp-jira-stdio 1.0.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,250 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import readline from 'readline';
6
+ import os from 'os';
7
+ import { fileURLToPath } from 'url';
8
+ import { execSync } from 'child_process';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ const rl = readline.createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout,
16
+ });
17
+
18
+ // Colors for terminal output
19
+ const colors = {
20
+ reset: '\x1b[0m',
21
+ bright: '\x1b[1m',
22
+ green: '\x1b[32m',
23
+ yellow: '\x1b[33m',
24
+ blue: '\x1b[34m',
25
+ red: '\x1b[31m',
26
+ };
27
+
28
+ function question(query) {
29
+ return new Promise((resolve) => rl.question(query, resolve));
30
+ }
31
+
32
+ function detectNodePath() {
33
+ try {
34
+ // First try to get current Node.js path
35
+ const nodeVersion = process.version;
36
+ console.log(`${colors.green}✓ Detected Node.js version: ${nodeVersion}${colors.reset}`);
37
+
38
+ // Check if using nvm
39
+ const nvmDir = process.env.NVM_DIR;
40
+ if (nvmDir) {
41
+ // Try to get the current version from nvm
42
+ try {
43
+ const currentVersion = execSync('nvm current', { encoding: 'utf8' }).trim();
44
+ const nodePath = path.join(nvmDir, 'versions', 'node', currentVersion, 'bin', 'node');
45
+ if (fs.existsSync(nodePath)) {
46
+ console.log(`${colors.green}✓ Using nvm Node.js at: ${nodePath}${colors.reset}`);
47
+ return nodePath;
48
+ }
49
+ } catch (e) {}
50
+ }
51
+
52
+ // Use the current Node.js executable path
53
+ console.log(`${colors.green}✓ Using Node.js at: ${process.execPath}${colors.reset}`);
54
+ return process.execPath;
55
+ } catch (error) {
56
+ console.log(`${colors.yellow}⚠️ Could not detect Node.js path, using default${colors.reset}`);
57
+ return 'node';
58
+ }
59
+ }
60
+
61
+ async function main() {
62
+ console.log(`${colors.bright}${colors.blue}🚀 MCP Jira Configuration Setup${colors.reset}\n`);
63
+
64
+ // Get project path
65
+ const projectPath = path.resolve(__dirname, '..');
66
+ const distIndexPath = path.join(projectPath, 'dist', 'index.js');
67
+
68
+ // Check if we need to build first
69
+ const srcIndexPath = path.join(projectPath, 'src', 'index.ts');
70
+ if (!fs.existsSync(distIndexPath) && fs.existsSync(srcIndexPath)) {
71
+ console.log(`${colors.yellow}⚠️ Dist file not found. Building project...${colors.reset}`);
72
+ try {
73
+ execSync('npm run build', { cwd: projectPath, stdio: 'inherit' });
74
+ console.log(`${colors.green}✓ Build completed${colors.reset}\n`);
75
+ } catch (error) {
76
+ console.log(
77
+ `${colors.red}❌ Build failed. Please run 'npm run build' manually${colors.reset}`
78
+ );
79
+ process.exit(1);
80
+ }
81
+ }
82
+
83
+ if (!fs.existsSync(distIndexPath)) {
84
+ console.log(`${colors.red}❌ Dist file not found at: ${distIndexPath}${colors.reset}`);
85
+ console.log(`${colors.yellow}Please run 'npm run build' first${colors.reset}`);
86
+ process.exit(1);
87
+ }
88
+
89
+ console.log(`${colors.green}✓ Found server dist at: ${distIndexPath}${colors.reset}\n`);
90
+
91
+ // Get Jira credentials
92
+ console.log(`${colors.bright}Jira Setup:${colors.reset}`);
93
+ let baseUrl = await question('Jira Base URL (e.g., https://your-instance.atlassian.net): ');
94
+ baseUrl = baseUrl.trim().replace(/\/$/, '');
95
+ const jiraEmail = await question('Jira Email: ');
96
+ const jiraApiToken = await question('Jira API Token: ');
97
+
98
+ // Detect Node.js path
99
+ const nodePath = detectNodePath();
100
+
101
+ // Ask user about Node.js configuration
102
+ console.log(`\n${colors.bright}Node.js Configuration:${colors.reset}`);
103
+ console.log(`1. Use detected Node.js: ${nodePath}`);
104
+ console.log(`2. Specify custom Node.js path`);
105
+ console.log(`3. Use system default (node)`);
106
+
107
+ const nodeChoice = await question('\nSelect option (1-3): ');
108
+ let finalNodePath = nodePath;
109
+
110
+ switch (nodeChoice) {
111
+ case '2':
112
+ finalNodePath = await question('Enter full path to Node.js executable: ');
113
+ if (!fs.existsSync(finalNodePath)) {
114
+ console.log(`${colors.yellow}⚠️ Path not found, using detected path${colors.reset}`);
115
+ finalNodePath = nodePath;
116
+ }
117
+ break;
118
+ case '3':
119
+ finalNodePath = 'node';
120
+ break;
121
+ }
122
+
123
+ // Create MCP config
124
+ const mcpConfig = {
125
+ mcpServers: {
126
+ jira: {
127
+ command: finalNodePath,
128
+ args: [distIndexPath],
129
+ env: {
130
+ JIRA_BASE_URL: baseUrl,
131
+ JIRA_EMAIL: jiraEmail,
132
+ JIRA_API_TOKEN: jiraApiToken,
133
+ },
134
+ },
135
+ },
136
+ };
137
+
138
+ // Determine config path
139
+ const platform = os.platform();
140
+ let configPath;
141
+ let configDir;
142
+
143
+ if (platform === 'darwin') {
144
+ configDir = path.join(os.homedir(), 'Library', 'Application Support', 'Claude');
145
+ configPath = path.join(configDir, 'claude_desktop_config.json');
146
+ } else if (platform === 'win32') {
147
+ configDir = path.join(os.homedir(), 'AppData', 'Roaming', 'Claude');
148
+ configPath = path.join(configDir, 'claude_desktop_config.json');
149
+ } else {
150
+ configDir = path.join(os.homedir(), '.config', 'claude');
151
+ configPath = path.join(configDir, 'claude_desktop_config.json');
152
+ }
153
+
154
+ console.log(`${colors.bright}Configuration Options:${colors.reset}`);
155
+ console.log('1. Save to Claude Desktop config');
156
+ console.log('2. Display config (copy manually)');
157
+ console.log('3. Save to custom file');
158
+
159
+ const choice = await question('\nSelect option (1-3): ');
160
+
161
+ switch (choice) {
162
+ case '1':
163
+ // Check if config exists
164
+ let existingConfig = {};
165
+ if (fs.existsSync(configPath)) {
166
+ try {
167
+ existingConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
168
+ if (existingConfig.mcpServers && existingConfig.mcpServers.jira) {
169
+ const overwrite = await question(
170
+ `\n${colors.yellow}⚠️ 'jira' server already exists. Overwrite? (y/n): ${colors.reset}`
171
+ );
172
+ if (overwrite.toLowerCase() !== 'y') {
173
+ console.log('Cancelled.');
174
+ process.exit(0);
175
+ }
176
+ delete existingConfig.mcpServers.jira;
177
+ }
178
+ } catch (error) {
179
+ console.log(
180
+ `${colors.yellow}⚠️ Could not read existing config, will create new one${colors.reset}`
181
+ );
182
+ }
183
+ }
184
+
185
+ // Merge configs
186
+ const finalConfig = {
187
+ ...existingConfig,
188
+ mcpServers: {
189
+ ...existingConfig.mcpServers,
190
+ ...mcpConfig.mcpServers,
191
+ },
192
+ };
193
+
194
+ if (!fs.existsSync(configDir)) {
195
+ fs.mkdirSync(configDir, { recursive: true });
196
+ }
197
+
198
+ fs.writeFileSync(configPath, JSON.stringify(finalConfig, null, 2));
199
+ console.log(`\n${colors.green}✅ Configuration saved to: ${configPath}${colors.reset}`);
200
+ console.log(`\n${colors.yellow}⚠️ Restart Claude Desktop to apply changes${colors.reset}`);
201
+ break;
202
+
203
+ case '2':
204
+ console.log(
205
+ `\n${colors.bright}Copy this configuration to your Claude Desktop config:${colors.reset}\n`
206
+ );
207
+ console.log(JSON.stringify(mcpConfig, null, 2));
208
+ break;
209
+
210
+ case '3':
211
+ const customPath = await question('Enter file path: ');
212
+ fs.writeFileSync(customPath, JSON.stringify(mcpConfig, null, 2));
213
+ console.log(`\n${colors.green}✅ Configuration saved to: ${customPath}${colors.reset}`);
214
+ break;
215
+
216
+ default:
217
+ console.log(`${colors.red}Invalid option${colors.reset}`);
218
+ }
219
+
220
+ // Show next steps
221
+ console.log(`\n${colors.bright}${colors.blue}Next Steps:${colors.reset}`);
222
+ console.log(`1. Restart Claude Desktop`);
223
+ console.log(
224
+ `2. Test Jira connection: ${colors.bright}node scripts/test-connection.cjs${colors.reset}`
225
+ );
226
+ console.log(`3. In Claude, try: "List my Jira projects"`);
227
+
228
+ // Create .env file option
229
+ const createEnv = await question(
230
+ `\n${colors.yellow}Create .env file for development? (y/n): ${colors.reset}`
231
+ );
232
+ if (createEnv.toLowerCase() === 'y') {
233
+ const envContent = `# Jira configuration
234
+ JIRA_BASE_URL=${baseUrl}
235
+ JIRA_EMAIL=${jiraEmail}
236
+ JIRA_API_TOKEN=${jiraApiToken}
237
+ `;
238
+
239
+ const envPath = path.join(projectPath, '.env');
240
+ fs.writeFileSync(envPath, envContent);
241
+ console.log(`\n${colors.green}✅ Created .env file for development${colors.reset}`);
242
+ }
243
+
244
+ rl.close();
245
+ }
246
+
247
+ main().catch((error) => {
248
+ console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
249
+ process.exit(1);
250
+ });
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Simple test script to verify Jira connection
4
+ const https = require('https');
5
+
6
+ // Load environment variables
7
+ require('dotenv').config();
8
+
9
+ const JIRA_BASE_URL = process.env.JIRA_BASE_URL;
10
+ const JIRA_EMAIL = process.env.JIRA_EMAIL;
11
+ const JIRA_API_TOKEN = process.env.JIRA_API_TOKEN;
12
+
13
+ console.log('Testing Jira Connection...');
14
+ console.log('========================');
15
+ console.log(`Base URL: ${JIRA_BASE_URL}`);
16
+ console.log(`Email: ${JIRA_EMAIL}`);
17
+ console.log(`Token: ***${JIRA_API_TOKEN?.slice(-4) || 'MISSING'}`);
18
+ console.log('========================\n');
19
+
20
+ // Parse URL
21
+ const url = new URL(`${JIRA_BASE_URL}/rest/api/3/myself`);
22
+
23
+ // Create auth string (Basic auth)
24
+ const auth = Buffer.from(`${JIRA_EMAIL}:${JIRA_API_TOKEN}`).toString('base64');
25
+
26
+ const options = {
27
+ hostname: url.hostname,
28
+ path: url.pathname,
29
+ method: 'GET',
30
+ headers: {
31
+ Authorization: `Basic ${auth}`,
32
+ Accept: 'application/json',
33
+ 'Content-Type': 'application/json',
34
+ },
35
+ };
36
+
37
+ console.log(`Making request to: ${url.toString()}`);
38
+
39
+ const req = https.request(options, (res) => {
40
+ console.log(`\nResponse Status: ${res.statusCode}`);
41
+
42
+ let data = '';
43
+
44
+ res.on('data', (chunk) => {
45
+ data += chunk;
46
+ });
47
+
48
+ res.on('end', () => {
49
+ try {
50
+ const json = JSON.parse(data);
51
+
52
+ if (res.statusCode === 200) {
53
+ console.log('\n✅ SUCCESS! Connection working!');
54
+ console.log('========================');
55
+ console.log('User Details:');
56
+ console.log(`- Display Name: ${json.displayName}`);
57
+ console.log(`- Email: ${json.emailAddress}`);
58
+ console.log(`- Account ID: ${json.accountId}`);
59
+ console.log(`- Active: ${json.active}`);
60
+ console.log('========================');
61
+
62
+ if (json.emailAddress !== JIRA_EMAIL) {
63
+ console.log('\n⚠️ WARNING: Email mismatch!');
64
+ console.log(`- .env email: ${JIRA_EMAIL}`);
65
+ console.log(`- Jira email: ${json.emailAddress}`);
66
+ console.log('\nYou should update JIRA_EMAIL in your .env file to: ' + json.emailAddress);
67
+ }
68
+ } else {
69
+ console.log('\n❌ FAILED! Authentication error');
70
+ console.log('Response:', json);
71
+
72
+ if (res.statusCode === 401) {
73
+ console.log('\nPossible issues:');
74
+ console.log('1. Wrong email address (check if it matches your Jira account)');
75
+ console.log('2. Invalid or expired API token');
76
+ console.log('3. Token needs to be regenerated');
77
+ }
78
+ }
79
+ } catch (e) {
80
+ console.log('Response body:', data);
81
+ }
82
+ });
83
+ });
84
+
85
+ req.on('error', (e) => {
86
+ console.error(`\n❌ Network Error: ${e.message}`);
87
+ });
88
+
89
+ req.end();