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.
- package/LICENSE +22 -0
- package/README.md +364 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2227 -0
- package/dist/index.js.map +1 -0
- package/package.json +87 -0
- package/scripts/setup-mcp-config.js +250 -0
- package/scripts/test-connection.cjs +89 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2227 @@
|
|
|
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 { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
15
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
16
|
+
}) : x)(function(x) {
|
|
17
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
18
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
19
|
+
});
|
|
20
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
21
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
22
|
+
};
|
|
23
|
+
var __copyProps = (to, from, except, desc) => {
|
|
24
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
25
|
+
for (let key of __getOwnPropNames(from))
|
|
26
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
27
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
28
|
+
}
|
|
29
|
+
return to;
|
|
30
|
+
};
|
|
31
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
32
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
33
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
34
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
35
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
36
|
+
__defProp(target, "default", { value: mod, enumerable: true }) ,
|
|
37
|
+
mod
|
|
38
|
+
));
|
|
39
|
+
|
|
40
|
+
// node_modules/dotenv/package.json
|
|
41
|
+
var require_package = __commonJS({
|
|
42
|
+
"node_modules/dotenv/package.json"(exports, module) {
|
|
43
|
+
module.exports = {
|
|
44
|
+
name: "dotenv",
|
|
45
|
+
version: "17.2.1",
|
|
46
|
+
description: "Loads environment variables from .env file",
|
|
47
|
+
main: "lib/main.js",
|
|
48
|
+
types: "lib/main.d.ts",
|
|
49
|
+
exports: {
|
|
50
|
+
".": {
|
|
51
|
+
types: "./lib/main.d.ts",
|
|
52
|
+
require: "./lib/main.js",
|
|
53
|
+
default: "./lib/main.js"
|
|
54
|
+
},
|
|
55
|
+
"./config": "./config.js",
|
|
56
|
+
"./config.js": "./config.js",
|
|
57
|
+
"./lib/env-options": "./lib/env-options.js",
|
|
58
|
+
"./lib/env-options.js": "./lib/env-options.js",
|
|
59
|
+
"./lib/cli-options": "./lib/cli-options.js",
|
|
60
|
+
"./lib/cli-options.js": "./lib/cli-options.js",
|
|
61
|
+
"./package.json": "./package.json"
|
|
62
|
+
},
|
|
63
|
+
scripts: {
|
|
64
|
+
"dts-check": "tsc --project tests/types/tsconfig.json",
|
|
65
|
+
lint: "standard",
|
|
66
|
+
pretest: "npm run lint && npm run dts-check",
|
|
67
|
+
test: "tap run --allow-empty-coverage --disable-coverage --timeout=60000",
|
|
68
|
+
"test:coverage": "tap run --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov",
|
|
69
|
+
prerelease: "npm test",
|
|
70
|
+
release: "standard-version"
|
|
71
|
+
},
|
|
72
|
+
repository: {
|
|
73
|
+
type: "git",
|
|
74
|
+
url: "git://github.com/motdotla/dotenv.git"
|
|
75
|
+
},
|
|
76
|
+
homepage: "https://github.com/motdotla/dotenv#readme",
|
|
77
|
+
funding: "https://dotenvx.com",
|
|
78
|
+
keywords: [
|
|
79
|
+
"dotenv",
|
|
80
|
+
"env",
|
|
81
|
+
".env",
|
|
82
|
+
"environment",
|
|
83
|
+
"variables",
|
|
84
|
+
"config",
|
|
85
|
+
"settings"
|
|
86
|
+
],
|
|
87
|
+
readmeFilename: "README.md",
|
|
88
|
+
license: "BSD-2-Clause",
|
|
89
|
+
devDependencies: {
|
|
90
|
+
"@types/node": "^18.11.3",
|
|
91
|
+
decache: "^4.6.2",
|
|
92
|
+
sinon: "^14.0.1",
|
|
93
|
+
standard: "^17.0.0",
|
|
94
|
+
"standard-version": "^9.5.0",
|
|
95
|
+
tap: "^19.2.0",
|
|
96
|
+
typescript: "^4.8.4"
|
|
97
|
+
},
|
|
98
|
+
engines: {
|
|
99
|
+
node: ">=12"
|
|
100
|
+
},
|
|
101
|
+
browser: {
|
|
102
|
+
fs: false
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// node_modules/dotenv/lib/main.js
|
|
109
|
+
var require_main = __commonJS({
|
|
110
|
+
"node_modules/dotenv/lib/main.js"(exports, module) {
|
|
111
|
+
var fs = __require("fs");
|
|
112
|
+
var path = __require("path");
|
|
113
|
+
var os = __require("os");
|
|
114
|
+
var crypto = __require("crypto");
|
|
115
|
+
var packageJson = require_package();
|
|
116
|
+
var version = packageJson.version;
|
|
117
|
+
var TIPS = [
|
|
118
|
+
"\u{1F510} encrypt with Dotenvx: https://dotenvx.com",
|
|
119
|
+
"\u{1F510} prevent committing .env to code: https://dotenvx.com/precommit",
|
|
120
|
+
"\u{1F510} prevent building .env in docker: https://dotenvx.com/prebuild",
|
|
121
|
+
"\u{1F4E1} observe env with Radar: https://dotenvx.com/radar",
|
|
122
|
+
"\u{1F4E1} auto-backup env with Radar: https://dotenvx.com/radar",
|
|
123
|
+
"\u{1F4E1} version env with Radar: https://dotenvx.com/radar",
|
|
124
|
+
"\u{1F6E0}\uFE0F run anywhere with `dotenvx run -- yourcommand`",
|
|
125
|
+
"\u2699\uFE0F specify custom .env file path with { path: '/custom/path/.env' }",
|
|
126
|
+
"\u2699\uFE0F enable debug logging with { debug: true }",
|
|
127
|
+
"\u2699\uFE0F override existing env vars with { override: true }",
|
|
128
|
+
"\u2699\uFE0F suppress all logs with { quiet: true }",
|
|
129
|
+
"\u2699\uFE0F write to custom object with { processEnv: myObject }",
|
|
130
|
+
"\u2699\uFE0F load multiple .env files with { path: ['.env.local', '.env'] }"
|
|
131
|
+
];
|
|
132
|
+
function _getRandomTip() {
|
|
133
|
+
return TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
134
|
+
}
|
|
135
|
+
function parseBoolean(value) {
|
|
136
|
+
if (typeof value === "string") {
|
|
137
|
+
return !["false", "0", "no", "off", ""].includes(value.toLowerCase());
|
|
138
|
+
}
|
|
139
|
+
return Boolean(value);
|
|
140
|
+
}
|
|
141
|
+
function supportsAnsi() {
|
|
142
|
+
return process.stdout.isTTY;
|
|
143
|
+
}
|
|
144
|
+
function dim(text) {
|
|
145
|
+
return supportsAnsi() ? `\x1B[2m${text}\x1B[0m` : text;
|
|
146
|
+
}
|
|
147
|
+
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
148
|
+
function parse(src) {
|
|
149
|
+
const obj = {};
|
|
150
|
+
let lines = src.toString();
|
|
151
|
+
lines = lines.replace(/\r\n?/mg, "\n");
|
|
152
|
+
let match;
|
|
153
|
+
while ((match = LINE.exec(lines)) != null) {
|
|
154
|
+
const key = match[1];
|
|
155
|
+
let value = match[2] || "";
|
|
156
|
+
value = value.trim();
|
|
157
|
+
const maybeQuote = value[0];
|
|
158
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, "$2");
|
|
159
|
+
if (maybeQuote === '"') {
|
|
160
|
+
value = value.replace(/\\n/g, "\n");
|
|
161
|
+
value = value.replace(/\\r/g, "\r");
|
|
162
|
+
}
|
|
163
|
+
obj[key] = value;
|
|
164
|
+
}
|
|
165
|
+
return obj;
|
|
166
|
+
}
|
|
167
|
+
function _parseVault(options) {
|
|
168
|
+
options = options || {};
|
|
169
|
+
const vaultPath = _vaultPath(options);
|
|
170
|
+
options.path = vaultPath;
|
|
171
|
+
const result = DotenvModule.configDotenv(options);
|
|
172
|
+
if (!result.parsed) {
|
|
173
|
+
const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
174
|
+
err.code = "MISSING_DATA";
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
const keys = _dotenvKey(options).split(",");
|
|
178
|
+
const length = keys.length;
|
|
179
|
+
let decrypted;
|
|
180
|
+
for (let i = 0; i < length; i++) {
|
|
181
|
+
try {
|
|
182
|
+
const key = keys[i].trim();
|
|
183
|
+
const attrs = _instructions(result, key);
|
|
184
|
+
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
185
|
+
break;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (i + 1 >= length) {
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return DotenvModule.parse(decrypted);
|
|
193
|
+
}
|
|
194
|
+
function _warn(message) {
|
|
195
|
+
console.error(`[dotenv@${version}][WARN] ${message}`);
|
|
196
|
+
}
|
|
197
|
+
function _debug(message) {
|
|
198
|
+
console.log(`[dotenv@${version}][DEBUG] ${message}`);
|
|
199
|
+
}
|
|
200
|
+
function _log(message) {
|
|
201
|
+
console.log(`[dotenv@${version}] ${message}`);
|
|
202
|
+
}
|
|
203
|
+
function _dotenvKey(options) {
|
|
204
|
+
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
|
|
205
|
+
return options.DOTENV_KEY;
|
|
206
|
+
}
|
|
207
|
+
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
|
|
208
|
+
return process.env.DOTENV_KEY;
|
|
209
|
+
}
|
|
210
|
+
return "";
|
|
211
|
+
}
|
|
212
|
+
function _instructions(result, dotenvKey) {
|
|
213
|
+
let uri;
|
|
214
|
+
try {
|
|
215
|
+
uri = new URL(dotenvKey);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (error.code === "ERR_INVALID_URL") {
|
|
218
|
+
const err = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
|
|
219
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
const key = uri.password;
|
|
225
|
+
if (!key) {
|
|
226
|
+
const err = new Error("INVALID_DOTENV_KEY: Missing key part");
|
|
227
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
230
|
+
const environment = uri.searchParams.get("environment");
|
|
231
|
+
if (!environment) {
|
|
232
|
+
const err = new Error("INVALID_DOTENV_KEY: Missing environment part");
|
|
233
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
234
|
+
throw err;
|
|
235
|
+
}
|
|
236
|
+
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
237
|
+
const ciphertext = result.parsed[environmentKey];
|
|
238
|
+
if (!ciphertext) {
|
|
239
|
+
const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
240
|
+
err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
|
|
241
|
+
throw err;
|
|
242
|
+
}
|
|
243
|
+
return { ciphertext, key };
|
|
244
|
+
}
|
|
245
|
+
function _vaultPath(options) {
|
|
246
|
+
let possibleVaultPath = null;
|
|
247
|
+
if (options && options.path && options.path.length > 0) {
|
|
248
|
+
if (Array.isArray(options.path)) {
|
|
249
|
+
for (const filepath of options.path) {
|
|
250
|
+
if (fs.existsSync(filepath)) {
|
|
251
|
+
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
|
|
259
|
+
}
|
|
260
|
+
if (fs.existsSync(possibleVaultPath)) {
|
|
261
|
+
return possibleVaultPath;
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
function _resolveHome(envPath) {
|
|
266
|
+
return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
|
|
267
|
+
}
|
|
268
|
+
function _configVault(options) {
|
|
269
|
+
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
270
|
+
const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
271
|
+
if (debug || !quiet) {
|
|
272
|
+
_log("Loading env from encrypted .env.vault");
|
|
273
|
+
}
|
|
274
|
+
const parsed = DotenvModule._parseVault(options);
|
|
275
|
+
let processEnv = process.env;
|
|
276
|
+
if (options && options.processEnv != null) {
|
|
277
|
+
processEnv = options.processEnv;
|
|
278
|
+
}
|
|
279
|
+
DotenvModule.populate(processEnv, parsed, options);
|
|
280
|
+
return { parsed };
|
|
281
|
+
}
|
|
282
|
+
function configDotenv(options) {
|
|
283
|
+
const dotenvPath = path.resolve(process.cwd(), ".env");
|
|
284
|
+
let encoding = "utf8";
|
|
285
|
+
let processEnv = process.env;
|
|
286
|
+
if (options && options.processEnv != null) {
|
|
287
|
+
processEnv = options.processEnv;
|
|
288
|
+
}
|
|
289
|
+
let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
290
|
+
let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
291
|
+
if (options && options.encoding) {
|
|
292
|
+
encoding = options.encoding;
|
|
293
|
+
} else {
|
|
294
|
+
if (debug) {
|
|
295
|
+
_debug("No encoding is specified. UTF-8 is used by default");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
let optionPaths = [dotenvPath];
|
|
299
|
+
if (options && options.path) {
|
|
300
|
+
if (!Array.isArray(options.path)) {
|
|
301
|
+
optionPaths = [_resolveHome(options.path)];
|
|
302
|
+
} else {
|
|
303
|
+
optionPaths = [];
|
|
304
|
+
for (const filepath of options.path) {
|
|
305
|
+
optionPaths.push(_resolveHome(filepath));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
let lastError;
|
|
310
|
+
const parsedAll = {};
|
|
311
|
+
for (const path2 of optionPaths) {
|
|
312
|
+
try {
|
|
313
|
+
const parsed = DotenvModule.parse(fs.readFileSync(path2, { encoding }));
|
|
314
|
+
DotenvModule.populate(parsedAll, parsed, options);
|
|
315
|
+
} catch (e) {
|
|
316
|
+
if (debug) {
|
|
317
|
+
_debug(`Failed to load ${path2} ${e.message}`);
|
|
318
|
+
}
|
|
319
|
+
lastError = e;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const populated = DotenvModule.populate(processEnv, parsedAll, options);
|
|
323
|
+
debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
|
|
324
|
+
quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
|
|
325
|
+
if (debug || !quiet) {
|
|
326
|
+
const keysCount = Object.keys(populated).length;
|
|
327
|
+
const shortPaths = [];
|
|
328
|
+
for (const filePath of optionPaths) {
|
|
329
|
+
try {
|
|
330
|
+
const relative = path.relative(process.cwd(), filePath);
|
|
331
|
+
shortPaths.push(relative);
|
|
332
|
+
} catch (e) {
|
|
333
|
+
if (debug) {
|
|
334
|
+
_debug(`Failed to load ${filePath} ${e.message}`);
|
|
335
|
+
}
|
|
336
|
+
lastError = e;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
_log(`injecting env (${keysCount}) from ${shortPaths.join(",")} ${dim(`-- tip: ${_getRandomTip()}`)}`);
|
|
340
|
+
}
|
|
341
|
+
if (lastError) {
|
|
342
|
+
return { parsed: parsedAll, error: lastError };
|
|
343
|
+
} else {
|
|
344
|
+
return { parsed: parsedAll };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function config(options) {
|
|
348
|
+
if (_dotenvKey(options).length === 0) {
|
|
349
|
+
return DotenvModule.configDotenv(options);
|
|
350
|
+
}
|
|
351
|
+
const vaultPath = _vaultPath(options);
|
|
352
|
+
if (!vaultPath) {
|
|
353
|
+
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
|
|
354
|
+
return DotenvModule.configDotenv(options);
|
|
355
|
+
}
|
|
356
|
+
return DotenvModule._configVault(options);
|
|
357
|
+
}
|
|
358
|
+
function decrypt(encrypted, keyStr) {
|
|
359
|
+
const key = Buffer.from(keyStr.slice(-64), "hex");
|
|
360
|
+
let ciphertext = Buffer.from(encrypted, "base64");
|
|
361
|
+
const nonce = ciphertext.subarray(0, 12);
|
|
362
|
+
const authTag = ciphertext.subarray(-16);
|
|
363
|
+
ciphertext = ciphertext.subarray(12, -16);
|
|
364
|
+
try {
|
|
365
|
+
const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
|
|
366
|
+
aesgcm.setAuthTag(authTag);
|
|
367
|
+
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
368
|
+
} catch (error) {
|
|
369
|
+
const isRange = error instanceof RangeError;
|
|
370
|
+
const invalidKeyLength = error.message === "Invalid key length";
|
|
371
|
+
const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
|
|
372
|
+
if (isRange || invalidKeyLength) {
|
|
373
|
+
const err = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
|
|
374
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
375
|
+
throw err;
|
|
376
|
+
} else if (decryptionFailed) {
|
|
377
|
+
const err = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
|
|
378
|
+
err.code = "DECRYPTION_FAILED";
|
|
379
|
+
throw err;
|
|
380
|
+
} else {
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function populate(processEnv, parsed, options = {}) {
|
|
386
|
+
const debug = Boolean(options && options.debug);
|
|
387
|
+
const override = Boolean(options && options.override);
|
|
388
|
+
const populated = {};
|
|
389
|
+
if (typeof parsed !== "object") {
|
|
390
|
+
const err = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
|
|
391
|
+
err.code = "OBJECT_REQUIRED";
|
|
392
|
+
throw err;
|
|
393
|
+
}
|
|
394
|
+
for (const key of Object.keys(parsed)) {
|
|
395
|
+
if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
396
|
+
if (override === true) {
|
|
397
|
+
processEnv[key] = parsed[key];
|
|
398
|
+
populated[key] = parsed[key];
|
|
399
|
+
}
|
|
400
|
+
if (debug) {
|
|
401
|
+
if (override === true) {
|
|
402
|
+
_debug(`"${key}" is already defined and WAS overwritten`);
|
|
403
|
+
} else {
|
|
404
|
+
_debug(`"${key}" is already defined and was NOT overwritten`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
processEnv[key] = parsed[key];
|
|
409
|
+
populated[key] = parsed[key];
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return populated;
|
|
413
|
+
}
|
|
414
|
+
var DotenvModule = {
|
|
415
|
+
configDotenv,
|
|
416
|
+
_configVault,
|
|
417
|
+
_parseVault,
|
|
418
|
+
config,
|
|
419
|
+
decrypt,
|
|
420
|
+
parse,
|
|
421
|
+
populate
|
|
422
|
+
};
|
|
423
|
+
module.exports.configDotenv = DotenvModule.configDotenv;
|
|
424
|
+
module.exports._configVault = DotenvModule._configVault;
|
|
425
|
+
module.exports._parseVault = DotenvModule._parseVault;
|
|
426
|
+
module.exports.config = DotenvModule.config;
|
|
427
|
+
module.exports.decrypt = DotenvModule.decrypt;
|
|
428
|
+
module.exports.parse = DotenvModule.parse;
|
|
429
|
+
module.exports.populate = DotenvModule.populate;
|
|
430
|
+
module.exports = DotenvModule;
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// package.json
|
|
435
|
+
var require_package2 = __commonJS({
|
|
436
|
+
"package.json"(exports, module) {
|
|
437
|
+
module.exports = {
|
|
438
|
+
name: "mcp-jira-stdio",
|
|
439
|
+
version: "1.0.0",
|
|
440
|
+
description: "Model Context Protocol (MCP) server for Jira API integration",
|
|
441
|
+
author: "graslt",
|
|
442
|
+
license: "MIT",
|
|
443
|
+
type: "module",
|
|
444
|
+
main: "dist/index.js",
|
|
445
|
+
types: "dist/index.d.ts",
|
|
446
|
+
bin: {
|
|
447
|
+
"mcp-jira-stdio": "./dist/index.js"
|
|
448
|
+
},
|
|
449
|
+
scripts: {
|
|
450
|
+
dev: "tsx watch src/index.ts",
|
|
451
|
+
build: "tsup",
|
|
452
|
+
start: "node dist/index.js",
|
|
453
|
+
clean: "rm -rf dist",
|
|
454
|
+
typecheck: "tsc --noEmit",
|
|
455
|
+
lint: "eslint src --ext .ts",
|
|
456
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
457
|
+
format: "prettier --write .",
|
|
458
|
+
"format:check": "prettier --check .",
|
|
459
|
+
check: "npm run typecheck && npm run lint && npm run format:check",
|
|
460
|
+
"check:all": "npm run check && npm run test:run && npm run build",
|
|
461
|
+
prepublishOnly: "npm run clean && npm run build",
|
|
462
|
+
inspector: "npx @modelcontextprotocol/inspector node dist/index.js",
|
|
463
|
+
"inspector:dev": "NODE_ENV=development npx @modelcontextprotocol/inspector npx tsx src/index.ts",
|
|
464
|
+
"setup:mcp": "node scripts/setup-mcp-config.js",
|
|
465
|
+
test: "vitest",
|
|
466
|
+
"test:run": "vitest run",
|
|
467
|
+
"test:coverage": "vitest run --coverage",
|
|
468
|
+
"test:watch": "vitest watch",
|
|
469
|
+
"test:ui": "vitest --ui",
|
|
470
|
+
"test:connection": "node scripts/test-connection.cjs"
|
|
471
|
+
},
|
|
472
|
+
keywords: [
|
|
473
|
+
"mcp",
|
|
474
|
+
"model-context-protocol",
|
|
475
|
+
"jira",
|
|
476
|
+
"atlassian",
|
|
477
|
+
"api",
|
|
478
|
+
"claude",
|
|
479
|
+
"ai"
|
|
480
|
+
],
|
|
481
|
+
engines: {
|
|
482
|
+
node: ">=18.0.0"
|
|
483
|
+
},
|
|
484
|
+
dependencies: {
|
|
485
|
+
"@modelcontextprotocol/sdk": "^1.17.1",
|
|
486
|
+
zod: "^3.22.4",
|
|
487
|
+
axios: "^1.7.0"
|
|
488
|
+
},
|
|
489
|
+
devDependencies: {
|
|
490
|
+
"@types/node": "^22.5.0",
|
|
491
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
492
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
493
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
494
|
+
"@vitest/ui": "^2.0.0",
|
|
495
|
+
dotenv: "^17.2.1",
|
|
496
|
+
eslint: "^9.9.0",
|
|
497
|
+
"eslint-config-prettier": "^9.1.0",
|
|
498
|
+
"eslint-plugin-prettier": "^5.2.0",
|
|
499
|
+
prettier: "^3.5.0",
|
|
500
|
+
tsup: "^8.0.0",
|
|
501
|
+
tsx: "^4.16.0",
|
|
502
|
+
typescript: "^5.5.0",
|
|
503
|
+
vitest: "^2.0.0"
|
|
504
|
+
},
|
|
505
|
+
files: [
|
|
506
|
+
"dist",
|
|
507
|
+
"README.md",
|
|
508
|
+
"LICENSE",
|
|
509
|
+
"scripts"
|
|
510
|
+
],
|
|
511
|
+
repository: {
|
|
512
|
+
type: "git",
|
|
513
|
+
url: "git+https://github.com/freema/mcp-jira-stdio.git"
|
|
514
|
+
},
|
|
515
|
+
bugs: {
|
|
516
|
+
url: "https://github.com/freema/mcp-jira-stdio/issues"
|
|
517
|
+
},
|
|
518
|
+
homepage: "https://github.com/freema/mcp-jira-stdio#readme",
|
|
519
|
+
publishConfig: {
|
|
520
|
+
access: "public"
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// src/config/constants.ts
|
|
527
|
+
var JIRA_CONFIG = {
|
|
528
|
+
BASE_PATH: "/rest/api/3",
|
|
529
|
+
DEFAULT_TIMEOUT: 3e4,
|
|
530
|
+
MAX_RETRIES: 3,
|
|
531
|
+
RETRY_DELAY: 1e3};
|
|
532
|
+
var ERROR_MESSAGES = {
|
|
533
|
+
AUTH_REQUIRED: "Jira authentication required. Set JIRA_BASE_URL, JIRA_EMAIL, and JIRA_API_TOKEN environment variables.",
|
|
534
|
+
INVALID_CREDENTIALS: "Invalid Jira credentials. Please check your email and API token.",
|
|
535
|
+
RATE_LIMIT_EXCEEDED: "Jira API rate limit exceeded. Please wait and try again.",
|
|
536
|
+
NETWORK_ERROR: "Network error occurred while connecting to Jira.",
|
|
537
|
+
VALIDATION_ERROR: "Input validation failed."
|
|
538
|
+
};
|
|
539
|
+
var TOOL_NAMES = {
|
|
540
|
+
GET_VISIBLE_PROJECTS: "jira_get_visible_projects",
|
|
541
|
+
GET_ISSUE: "jira_get_issue",
|
|
542
|
+
SEARCH_ISSUES: "jira_search_issues",
|
|
543
|
+
GET_MY_ISSUES: "jira_get_my_issues",
|
|
544
|
+
GET_ISSUE_TYPES: "jira_get_issue_types",
|
|
545
|
+
GET_USERS: "jira_get_users",
|
|
546
|
+
GET_PRIORITIES: "jira_get_priorities",
|
|
547
|
+
GET_STATUSES: "jira_get_statuses",
|
|
548
|
+
CREATE_ISSUE: "jira_create_issue",
|
|
549
|
+
UPDATE_ISSUE: "jira_update_issue",
|
|
550
|
+
ADD_COMMENT: "jira_add_comment",
|
|
551
|
+
GET_PROJECT_INFO: "jira_get_project_info",
|
|
552
|
+
CREATE_SUBTASK: "jira_create_subtask"
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// src/utils/logger.ts
|
|
556
|
+
var levels = {
|
|
557
|
+
debug: 10,
|
|
558
|
+
info: 20,
|
|
559
|
+
warn: 30,
|
|
560
|
+
error: 40,
|
|
561
|
+
silent: 99
|
|
562
|
+
};
|
|
563
|
+
function getLevel() {
|
|
564
|
+
const env = (process.env.LOG_LEVEL || (process.env.NODE_ENV === "development" ? "debug" : "info")).toLowerCase();
|
|
565
|
+
if (env === "debug" || env === "info" || env === "warn" || env === "error" || env === "silent")
|
|
566
|
+
return env;
|
|
567
|
+
return "info";
|
|
568
|
+
}
|
|
569
|
+
function createLogger(scope) {
|
|
570
|
+
const level = getLevel();
|
|
571
|
+
const min = levels[level];
|
|
572
|
+
const fmt = (msg) => typeof msg === "string" ? msg : JSON.stringify(msg);
|
|
573
|
+
const prefix = (lvl) => `[${lvl.toUpperCase()}] ${scope}:`;
|
|
574
|
+
return {
|
|
575
|
+
debug: (...args) => {
|
|
576
|
+
if (min <= levels.debug) console.error(prefix("debug"), ...args.map(fmt));
|
|
577
|
+
},
|
|
578
|
+
info: (...args) => {
|
|
579
|
+
if (min <= levels.info) console.error(prefix("info"), ...args.map(fmt));
|
|
580
|
+
},
|
|
581
|
+
warn: (...args) => {
|
|
582
|
+
if (min <= levels.warn) console.error(prefix("warn"), ...args.map(fmt));
|
|
583
|
+
},
|
|
584
|
+
error: (...args) => {
|
|
585
|
+
if (min <= levels.error) console.error(prefix("error"), ...args.map(fmt));
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/utils/jira-auth.ts
|
|
591
|
+
var jiraClient = null;
|
|
592
|
+
var log = createLogger("jira-auth");
|
|
593
|
+
function validateAuth() {
|
|
594
|
+
const baseUrl = process.env.JIRA_BASE_URL;
|
|
595
|
+
const email = process.env.JIRA_EMAIL;
|
|
596
|
+
const apiToken = process.env.JIRA_API_TOKEN;
|
|
597
|
+
if (!baseUrl || !email || !apiToken) {
|
|
598
|
+
throw new Error(ERROR_MESSAGES.AUTH_REQUIRED);
|
|
599
|
+
}
|
|
600
|
+
const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
601
|
+
return {
|
|
602
|
+
baseUrl: normalizedBaseUrl,
|
|
603
|
+
email,
|
|
604
|
+
apiToken
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function getAuthenticatedClient() {
|
|
608
|
+
if (jiraClient) {
|
|
609
|
+
return jiraClient;
|
|
610
|
+
}
|
|
611
|
+
const auth = validateAuth();
|
|
612
|
+
jiraClient = axios.create({
|
|
613
|
+
baseURL: `${auth.baseUrl}${JIRA_CONFIG.BASE_PATH}`,
|
|
614
|
+
timeout: JIRA_CONFIG.DEFAULT_TIMEOUT,
|
|
615
|
+
headers: {
|
|
616
|
+
Accept: "application/json",
|
|
617
|
+
"Content-Type": "application/json"
|
|
618
|
+
},
|
|
619
|
+
auth: {
|
|
620
|
+
username: auth.email,
|
|
621
|
+
password: auth.apiToken
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
jiraClient.interceptors.request.use(
|
|
625
|
+
(config) => {
|
|
626
|
+
log.debug(`Request: ${config.method?.toUpperCase()} ${config.baseURL}${config.url}`);
|
|
627
|
+
return config;
|
|
628
|
+
},
|
|
629
|
+
(error) => {
|
|
630
|
+
log.error("Request error:", error);
|
|
631
|
+
return Promise.reject(error);
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
jiraClient.interceptors.response.use(
|
|
635
|
+
(response) => {
|
|
636
|
+
log.debug(`Response: ${response.status} ${response.config.url}`);
|
|
637
|
+
return response;
|
|
638
|
+
},
|
|
639
|
+
(error) => {
|
|
640
|
+
log.error(`Jira API error: ${error.response?.status} ${error.config?.url}`);
|
|
641
|
+
if (error.response?.status === 401) {
|
|
642
|
+
throw new Error(ERROR_MESSAGES.INVALID_CREDENTIALS);
|
|
643
|
+
} else if (error.response?.status === 404) {
|
|
644
|
+
throw new Error("Resource not found or insufficient permissions");
|
|
645
|
+
} else if (error.response?.status === 429) {
|
|
646
|
+
throw new Error(ERROR_MESSAGES.RATE_LIMIT_EXCEEDED);
|
|
647
|
+
} else if (!error.response) {
|
|
648
|
+
throw new Error(ERROR_MESSAGES.NETWORK_ERROR);
|
|
649
|
+
}
|
|
650
|
+
return Promise.reject(error);
|
|
651
|
+
}
|
|
652
|
+
);
|
|
653
|
+
return jiraClient;
|
|
654
|
+
}
|
|
655
|
+
async function makeJiraRequest(config, retryConfig = {
|
|
656
|
+
maxRetries: JIRA_CONFIG.MAX_RETRIES,
|
|
657
|
+
retryDelay: JIRA_CONFIG.RETRY_DELAY
|
|
658
|
+
}) {
|
|
659
|
+
const client = getAuthenticatedClient();
|
|
660
|
+
let lastError;
|
|
661
|
+
for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
|
|
662
|
+
try {
|
|
663
|
+
const response = await client.request(config);
|
|
664
|
+
return response.data;
|
|
665
|
+
} catch (error) {
|
|
666
|
+
lastError = error;
|
|
667
|
+
if (error.response?.status === 401 || error.response?.status === 403 || error.response?.status === 404 || error.response?.status >= 400 && error.response?.status < 500 && error.response?.status !== 429) {
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
if (retryConfig.retryCondition && !retryConfig.retryCondition(error)) {
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
if (attempt === retryConfig.maxRetries) {
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
const retryAfterHeader = error.response?.headers?.["retry-after"];
|
|
677
|
+
let delayMs = 0;
|
|
678
|
+
if (retryAfterHeader) {
|
|
679
|
+
const seconds = Number(retryAfterHeader);
|
|
680
|
+
delayMs = !Number.isNaN(seconds) ? seconds * 1e3 : retryConfig.retryDelay;
|
|
681
|
+
} else {
|
|
682
|
+
const base = retryConfig.retryDelay * Math.pow(2, attempt);
|
|
683
|
+
const jitter = Math.floor(Math.random() * (retryConfig.retryDelay / 2));
|
|
684
|
+
delayMs = base + jitter;
|
|
685
|
+
}
|
|
686
|
+
log.warn(
|
|
687
|
+
`Retrying request in ${delayMs}ms (attempt ${attempt + 1}/${retryConfig.maxRetries})`
|
|
688
|
+
);
|
|
689
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
throw lastError;
|
|
693
|
+
}
|
|
694
|
+
async function testConnection() {
|
|
695
|
+
try {
|
|
696
|
+
await makeJiraRequest({
|
|
697
|
+
method: "GET",
|
|
698
|
+
url: "/myself"
|
|
699
|
+
});
|
|
700
|
+
return true;
|
|
701
|
+
} catch (error) {
|
|
702
|
+
log.error("Connection test failed:", error);
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function validateInput(schema, input) {
|
|
707
|
+
try {
|
|
708
|
+
return schema.parse(input);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
if (error instanceof z.ZodError) {
|
|
711
|
+
const errorMessages = error.errors.map((err) => `${err.path.join(".")}: ${err.message}`);
|
|
712
|
+
throw new Error(`${ERROR_MESSAGES.VALIDATION_ERROR}
|
|
713
|
+
${errorMessages.join("\n")}`);
|
|
714
|
+
}
|
|
715
|
+
throw error;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function isValidIssueKey(key) {
|
|
719
|
+
return /^[A-Z][A-Z0-9]*-\d+$/.test(key);
|
|
720
|
+
}
|
|
721
|
+
function isValidProjectKey(key) {
|
|
722
|
+
return /^[A-Z][A-Z0-9]{0,9}$/.test(key);
|
|
723
|
+
}
|
|
724
|
+
function sanitizeJQL(jql) {
|
|
725
|
+
return jql.replace(/[<>"'`]/g, "").replace(/\s+/g, " ").trim();
|
|
726
|
+
}
|
|
727
|
+
function validatePagination(startAt, maxResults) {
|
|
728
|
+
if (startAt < 0) {
|
|
729
|
+
throw new Error("startAt must be non-negative");
|
|
730
|
+
}
|
|
731
|
+
if (maxResults < 1 || maxResults > 100) {
|
|
732
|
+
throw new Error("maxResults must be between 1 and 100");
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// src/types/tools.ts
|
|
737
|
+
var GetVisibleProjectsInputSchema = z.object({
|
|
738
|
+
expand: z.array(z.string()).optional().describe("Additional project details to include"),
|
|
739
|
+
recent: z.number().optional().describe("Limit to recently accessed projects")
|
|
740
|
+
});
|
|
741
|
+
var GetIssueInputSchema = z.object({
|
|
742
|
+
issueKey: z.string().min(1).describe(
|
|
743
|
+
"Issue key or full Jira URL (e.g., PROJECT-123 or https://your.atlassian.net/browse/PROJECT-123)"
|
|
744
|
+
),
|
|
745
|
+
expand: z.array(z.string()).optional().describe("Additional issue details to include"),
|
|
746
|
+
fields: z.array(z.string()).optional().describe("Specific fields to retrieve")
|
|
747
|
+
});
|
|
748
|
+
var SearchIssuesInputSchema = z.object({
|
|
749
|
+
jql: z.string().min(1).describe("JQL query string"),
|
|
750
|
+
startAt: z.number().min(0).default(0).describe("Index of first result to return"),
|
|
751
|
+
maxResults: z.number().min(1).max(100).default(50).describe("Maximum number of results to return"),
|
|
752
|
+
fields: z.array(z.string()).optional().describe("Specific fields to retrieve"),
|
|
753
|
+
expand: z.array(z.string()).optional().describe("Additional details to include")
|
|
754
|
+
});
|
|
755
|
+
var CreateIssueInputSchema = z.object({
|
|
756
|
+
projectKey: z.string().describe("Project key where the issue will be created").refine((v) => isValidProjectKey(v), "Invalid project key format"),
|
|
757
|
+
summary: z.string().min(1).describe("Issue summary/title"),
|
|
758
|
+
description: z.string().optional().describe("Issue description"),
|
|
759
|
+
issueType: z.string().describe("Issue type (e.g., Bug, Story, Task)"),
|
|
760
|
+
priority: z.string().optional().describe("Issue priority"),
|
|
761
|
+
assignee: z.string().optional().describe("Assignee account ID"),
|
|
762
|
+
labels: z.array(z.string()).optional().describe("Issue labels"),
|
|
763
|
+
components: z.array(z.string()).optional().describe("Component names"),
|
|
764
|
+
returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after creation")
|
|
765
|
+
});
|
|
766
|
+
var UpdateIssueInputSchema = z.object({
|
|
767
|
+
issueKey: z.string().describe("Issue key to update").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
|
|
768
|
+
summary: z.string().optional().describe("New summary"),
|
|
769
|
+
description: z.string().optional().describe("New description"),
|
|
770
|
+
priority: z.string().optional().describe("New priority"),
|
|
771
|
+
assignee: z.string().optional().describe("New assignee account ID"),
|
|
772
|
+
labels: z.array(z.string()).optional().describe("New labels (replaces existing)"),
|
|
773
|
+
components: z.array(z.string()).optional().describe("New components (replaces existing)"),
|
|
774
|
+
returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after update")
|
|
775
|
+
});
|
|
776
|
+
var GetMyIssuesInputSchema = z.object({
|
|
777
|
+
startAt: z.number().min(0).default(0).describe("Index of first result to return"),
|
|
778
|
+
maxResults: z.number().min(1).max(100).default(50).describe("Maximum number of results to return"),
|
|
779
|
+
fields: z.array(z.string()).optional().describe("Specific fields to retrieve"),
|
|
780
|
+
expand: z.array(z.string()).optional().describe("Additional details to include")
|
|
781
|
+
});
|
|
782
|
+
var GetIssueTypesInputSchema = z.object({
|
|
783
|
+
projectKey: z.string().optional().describe("Project key to get issue types for specific project").refine((v) => v ? isValidProjectKey(v) : true, "Invalid project key format")
|
|
784
|
+
});
|
|
785
|
+
var GetUsersInputSchema = z.object({
|
|
786
|
+
query: z.string().optional().describe("Search query for user name or email"),
|
|
787
|
+
username: z.string().optional().describe("Specific username to search for"),
|
|
788
|
+
accountId: z.string().optional().describe("Specific account ID to search for"),
|
|
789
|
+
startAt: z.number().min(0).default(0).describe("Index of first result to return"),
|
|
790
|
+
maxResults: z.number().min(1).max(50).default(50).describe("Maximum number of results to return")
|
|
791
|
+
});
|
|
792
|
+
var GetPrioritiesInputSchema = z.object({});
|
|
793
|
+
var GetStatusesInputSchema = z.object({
|
|
794
|
+
projectKey: z.string().optional().describe("Project key to get statuses for specific project").refine((v) => v ? isValidProjectKey(v) : true, "Invalid project key format"),
|
|
795
|
+
issueTypeId: z.string().optional().describe("Issue type ID to get statuses for specific issue type")
|
|
796
|
+
});
|
|
797
|
+
var AddCommentInputSchema = z.object({
|
|
798
|
+
issueKey: z.string().describe("Issue key to add comment to").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
|
|
799
|
+
body: z.string().min(1).describe("Comment body text"),
|
|
800
|
+
visibility: z.object({
|
|
801
|
+
type: z.enum(["group", "role"]).describe("Visibility type"),
|
|
802
|
+
value: z.string().describe("Group name or role name")
|
|
803
|
+
}).optional().describe("Comment visibility restrictions")
|
|
804
|
+
});
|
|
805
|
+
var GetProjectInfoInputSchema = z.object({
|
|
806
|
+
projectKey: z.string().describe("Project key to get detailed information for").refine((v) => isValidProjectKey(v), "Invalid project key format"),
|
|
807
|
+
expand: z.array(z.string()).optional().describe("Additional project details to include")
|
|
808
|
+
});
|
|
809
|
+
var CreateSubtaskInputSchema = z.object({
|
|
810
|
+
parentIssueKey: z.string().describe("Parent issue key"),
|
|
811
|
+
summary: z.string().min(1).describe("Subtask summary/title"),
|
|
812
|
+
description: z.string().optional().describe("Subtask description"),
|
|
813
|
+
priority: z.string().optional().describe("Subtask priority"),
|
|
814
|
+
assignee: z.string().optional().describe("Assignee account ID"),
|
|
815
|
+
labels: z.array(z.string()).optional().describe("Subtask labels"),
|
|
816
|
+
components: z.array(z.string()).optional().describe("Component names")
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
// src/utils/api-helpers.ts
|
|
820
|
+
async function getVisibleProjects(options = {}) {
|
|
821
|
+
const params = {};
|
|
822
|
+
if (options.expand) {
|
|
823
|
+
params.expand = options.expand.join(",");
|
|
824
|
+
}
|
|
825
|
+
if (options.recent) {
|
|
826
|
+
params.recent = options.recent;
|
|
827
|
+
}
|
|
828
|
+
const config = {
|
|
829
|
+
method: "GET",
|
|
830
|
+
url: "/project/search",
|
|
831
|
+
params
|
|
832
|
+
};
|
|
833
|
+
const response = await makeJiraRequest(config);
|
|
834
|
+
return response.values;
|
|
835
|
+
}
|
|
836
|
+
async function getIssue(issueKey, options = {}) {
|
|
837
|
+
const params = {};
|
|
838
|
+
if (options.expand) {
|
|
839
|
+
params.expand = options.expand.join(",");
|
|
840
|
+
}
|
|
841
|
+
if (options.fields) {
|
|
842
|
+
params.fields = options.fields.join(",");
|
|
843
|
+
}
|
|
844
|
+
const config = {
|
|
845
|
+
method: "GET",
|
|
846
|
+
url: `/issue/${issueKey}`,
|
|
847
|
+
params
|
|
848
|
+
};
|
|
849
|
+
return await makeJiraRequest(config);
|
|
850
|
+
}
|
|
851
|
+
async function searchIssues(options) {
|
|
852
|
+
const { jql, startAt = 0, maxResults = 50 } = options;
|
|
853
|
+
validatePagination(startAt, maxResults);
|
|
854
|
+
const sanitizedJql = sanitizeJQL(jql);
|
|
855
|
+
const data = {
|
|
856
|
+
jql: sanitizedJql,
|
|
857
|
+
startAt,
|
|
858
|
+
maxResults
|
|
859
|
+
};
|
|
860
|
+
if (options.fields) {
|
|
861
|
+
data.fields = options.fields;
|
|
862
|
+
}
|
|
863
|
+
if (options.expand) {
|
|
864
|
+
data.expand = options.expand;
|
|
865
|
+
}
|
|
866
|
+
const config = {
|
|
867
|
+
method: "POST",
|
|
868
|
+
url: "/search",
|
|
869
|
+
data
|
|
870
|
+
};
|
|
871
|
+
return await makeJiraRequest(config);
|
|
872
|
+
}
|
|
873
|
+
async function createIssue(issueData, options = { returnIssue: true }) {
|
|
874
|
+
const fields = {
|
|
875
|
+
project: { key: issueData.projectKey },
|
|
876
|
+
summary: issueData.summary,
|
|
877
|
+
issuetype: { name: issueData.issueType }
|
|
878
|
+
};
|
|
879
|
+
if (issueData.description) {
|
|
880
|
+
fields.description = issueData.description;
|
|
881
|
+
}
|
|
882
|
+
if (issueData.priority) {
|
|
883
|
+
fields.priority = { name: issueData.priority };
|
|
884
|
+
}
|
|
885
|
+
if (issueData.assignee) {
|
|
886
|
+
fields.assignee = { accountId: issueData.assignee };
|
|
887
|
+
}
|
|
888
|
+
if (issueData.labels && issueData.labels.length > 0) {
|
|
889
|
+
fields.labels = issueData.labels;
|
|
890
|
+
}
|
|
891
|
+
if (issueData.components && issueData.components.length > 0) {
|
|
892
|
+
fields.components = issueData.components.map((name) => ({ name }));
|
|
893
|
+
}
|
|
894
|
+
const config = {
|
|
895
|
+
method: "POST",
|
|
896
|
+
url: "/issue",
|
|
897
|
+
data: { fields }
|
|
898
|
+
};
|
|
899
|
+
const response = await makeJiraRequest(config);
|
|
900
|
+
if (options.returnIssue === false) {
|
|
901
|
+
return response.key;
|
|
902
|
+
}
|
|
903
|
+
return await getIssue(response.key);
|
|
904
|
+
}
|
|
905
|
+
async function updateIssue(issueKey, updates) {
|
|
906
|
+
const fields = {};
|
|
907
|
+
if (updates.summary !== void 0) {
|
|
908
|
+
fields.summary = updates.summary;
|
|
909
|
+
}
|
|
910
|
+
if (updates.description !== void 0) {
|
|
911
|
+
fields.description = updates.description;
|
|
912
|
+
}
|
|
913
|
+
if (updates.priority !== void 0) {
|
|
914
|
+
fields.priority = { name: updates.priority };
|
|
915
|
+
}
|
|
916
|
+
if (updates.assignee !== void 0) {
|
|
917
|
+
fields.assignee = updates.assignee ? { accountId: updates.assignee } : null;
|
|
918
|
+
}
|
|
919
|
+
if (updates.labels !== void 0) {
|
|
920
|
+
fields.labels = updates.labels;
|
|
921
|
+
}
|
|
922
|
+
if (updates.components !== void 0) {
|
|
923
|
+
fields.components = updates.components.map((name) => ({ name }));
|
|
924
|
+
}
|
|
925
|
+
const config = {
|
|
926
|
+
method: "PUT",
|
|
927
|
+
url: `/issue/${issueKey}`,
|
|
928
|
+
data: { fields }
|
|
929
|
+
};
|
|
930
|
+
await makeJiraRequest(config);
|
|
931
|
+
}
|
|
932
|
+
async function getMyIssues(options = {}) {
|
|
933
|
+
const jql = `assignee = currentUser() ORDER BY updated DESC`;
|
|
934
|
+
const searchParams = {
|
|
935
|
+
jql,
|
|
936
|
+
startAt: options.startAt || 0,
|
|
937
|
+
maxResults: options.maxResults || 50
|
|
938
|
+
};
|
|
939
|
+
if (options.fields !== void 0) searchParams.fields = options.fields;
|
|
940
|
+
if (options.expand !== void 0) searchParams.expand = options.expand;
|
|
941
|
+
return await searchIssues(searchParams);
|
|
942
|
+
}
|
|
943
|
+
async function getIssueTypes(projectKey) {
|
|
944
|
+
let url = "/issuetype";
|
|
945
|
+
if (projectKey) {
|
|
946
|
+
url = `/project/${projectKey}/issuetype`;
|
|
947
|
+
}
|
|
948
|
+
const config = {
|
|
949
|
+
method: "GET",
|
|
950
|
+
url
|
|
951
|
+
};
|
|
952
|
+
return await makeJiraRequest(config);
|
|
953
|
+
}
|
|
954
|
+
async function getUsers(options = {}) {
|
|
955
|
+
const params = {};
|
|
956
|
+
if (options.query) {
|
|
957
|
+
params.query = options.query;
|
|
958
|
+
}
|
|
959
|
+
if (options.username) {
|
|
960
|
+
params.username = options.username;
|
|
961
|
+
}
|
|
962
|
+
if (options.accountId) {
|
|
963
|
+
params.accountId = options.accountId;
|
|
964
|
+
}
|
|
965
|
+
if (options.startAt !== void 0) {
|
|
966
|
+
params.startAt = options.startAt;
|
|
967
|
+
}
|
|
968
|
+
if (options.maxResults !== void 0) {
|
|
969
|
+
params.maxResults = options.maxResults;
|
|
970
|
+
}
|
|
971
|
+
const config = {
|
|
972
|
+
method: "GET",
|
|
973
|
+
url: "/user/search",
|
|
974
|
+
params
|
|
975
|
+
};
|
|
976
|
+
return await makeJiraRequest(config);
|
|
977
|
+
}
|
|
978
|
+
async function getPriorities() {
|
|
979
|
+
const config = {
|
|
980
|
+
method: "GET",
|
|
981
|
+
url: "/priority"
|
|
982
|
+
};
|
|
983
|
+
return await makeJiraRequest(config);
|
|
984
|
+
}
|
|
985
|
+
async function getStatuses(options = {}) {
|
|
986
|
+
let url = "/status";
|
|
987
|
+
if (options.projectKey && options.issueTypeId) {
|
|
988
|
+
url = `/project/${options.projectKey}/statuses`;
|
|
989
|
+
} else if (options.projectKey) {
|
|
990
|
+
url = `/project/${options.projectKey}/statuses`;
|
|
991
|
+
}
|
|
992
|
+
const config = {
|
|
993
|
+
method: "GET",
|
|
994
|
+
url
|
|
995
|
+
};
|
|
996
|
+
if (options.projectKey) {
|
|
997
|
+
const response = await makeJiraRequest(config);
|
|
998
|
+
if (options.issueTypeId) {
|
|
999
|
+
const match = response.find(
|
|
1000
|
+
(issueType) => issueType.id === options.issueTypeId || issueType.name === options.issueTypeId
|
|
1001
|
+
);
|
|
1002
|
+
if (match) return match.statuses;
|
|
1003
|
+
}
|
|
1004
|
+
return response.flatMap((issueType) => issueType.statuses);
|
|
1005
|
+
}
|
|
1006
|
+
return await makeJiraRequest(config);
|
|
1007
|
+
}
|
|
1008
|
+
async function addComment(issueKey, body, visibility) {
|
|
1009
|
+
const data = { body };
|
|
1010
|
+
if (visibility) {
|
|
1011
|
+
data.visibility = visibility;
|
|
1012
|
+
}
|
|
1013
|
+
const config = {
|
|
1014
|
+
method: "POST",
|
|
1015
|
+
url: `/issue/${issueKey}/comment`,
|
|
1016
|
+
data
|
|
1017
|
+
};
|
|
1018
|
+
return await makeJiraRequest(config);
|
|
1019
|
+
}
|
|
1020
|
+
async function getProjectDetails(projectKey, expand) {
|
|
1021
|
+
const params = {};
|
|
1022
|
+
if (expand) {
|
|
1023
|
+
params.expand = expand.join(",");
|
|
1024
|
+
}
|
|
1025
|
+
const config = {
|
|
1026
|
+
method: "GET",
|
|
1027
|
+
url: `/project/${projectKey}`,
|
|
1028
|
+
params
|
|
1029
|
+
};
|
|
1030
|
+
return await makeJiraRequest(config);
|
|
1031
|
+
}
|
|
1032
|
+
async function createSubtask(parentIssueKey, subtaskData) {
|
|
1033
|
+
const parentIssue = await getIssue(parentIssueKey);
|
|
1034
|
+
const projectKey = parentIssue.fields.project.key;
|
|
1035
|
+
const issueTypes = await getIssueTypes(projectKey);
|
|
1036
|
+
const subtaskType = issueTypes.find((type) => type.subtask);
|
|
1037
|
+
if (!subtaskType) {
|
|
1038
|
+
throw new Error(`No subtask issue type found for project ${projectKey}`);
|
|
1039
|
+
}
|
|
1040
|
+
const fields = {
|
|
1041
|
+
project: { key: projectKey },
|
|
1042
|
+
parent: { key: parentIssueKey },
|
|
1043
|
+
summary: subtaskData.summary,
|
|
1044
|
+
issuetype: { id: subtaskType.id }
|
|
1045
|
+
};
|
|
1046
|
+
if (subtaskData.description) {
|
|
1047
|
+
fields.description = subtaskData.description;
|
|
1048
|
+
}
|
|
1049
|
+
if (subtaskData.priority) {
|
|
1050
|
+
fields.priority = { name: subtaskData.priority };
|
|
1051
|
+
}
|
|
1052
|
+
if (subtaskData.assignee) {
|
|
1053
|
+
fields.assignee = { accountId: subtaskData.assignee };
|
|
1054
|
+
}
|
|
1055
|
+
if (subtaskData.labels && subtaskData.labels.length > 0) {
|
|
1056
|
+
fields.labels = subtaskData.labels;
|
|
1057
|
+
}
|
|
1058
|
+
if (subtaskData.components && subtaskData.components.length > 0) {
|
|
1059
|
+
fields.components = subtaskData.components.map((name) => ({ name }));
|
|
1060
|
+
}
|
|
1061
|
+
const config = {
|
|
1062
|
+
method: "POST",
|
|
1063
|
+
url: "/issue",
|
|
1064
|
+
data: { fields }
|
|
1065
|
+
};
|
|
1066
|
+
const response = await makeJiraRequest(config);
|
|
1067
|
+
return await getIssue(response.key);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// src/utils/redaction.ts
|
|
1071
|
+
function isRedactionEnabled() {
|
|
1072
|
+
const val = process.env.MCP_REDACT_SENSITIVE || process.env.REDACT_SENSITIVE;
|
|
1073
|
+
return val === "1" || val?.toLowerCase() === "true";
|
|
1074
|
+
}
|
|
1075
|
+
function maybeRedactAccountId(accountId) {
|
|
1076
|
+
if (!accountId) return "N/A";
|
|
1077
|
+
if (!isRedactionEnabled()) return accountId;
|
|
1078
|
+
const head = accountId.slice(0, 4);
|
|
1079
|
+
return `${head}\u2026`;
|
|
1080
|
+
}
|
|
1081
|
+
function maybeRedactEmail(email) {
|
|
1082
|
+
if (!email) return "N/A";
|
|
1083
|
+
if (!isRedactionEnabled()) return email;
|
|
1084
|
+
return "hidden";
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// src/utils/formatters.ts
|
|
1088
|
+
function formatProjectsResponse(projects) {
|
|
1089
|
+
const projectsList = projects.map(
|
|
1090
|
+
(project) => `\u2022 **${project.key}** - ${project.name}
|
|
1091
|
+
${project.description || "No description"}
|
|
1092
|
+
Type: ${project.projectTypeKey} | Private: ${project.isPrivate ? "Yes" : "No"}`
|
|
1093
|
+
).join("\n\n");
|
|
1094
|
+
return {
|
|
1095
|
+
content: [
|
|
1096
|
+
{
|
|
1097
|
+
type: "text",
|
|
1098
|
+
text: `Found ${projects.length} visible project(s):
|
|
1099
|
+
|
|
1100
|
+
${projectsList}`
|
|
1101
|
+
}
|
|
1102
|
+
]
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function adfToPlainText(adf) {
|
|
1106
|
+
try {
|
|
1107
|
+
const walk = (node) => {
|
|
1108
|
+
if (!node) return "";
|
|
1109
|
+
switch (node.type) {
|
|
1110
|
+
case "text":
|
|
1111
|
+
return node.text || "";
|
|
1112
|
+
case "paragraph":
|
|
1113
|
+
case "heading":
|
|
1114
|
+
case "blockquote":
|
|
1115
|
+
return (node.content || []).map(walk).join("");
|
|
1116
|
+
case "codeBlock":
|
|
1117
|
+
return "\n" + (node.content || []).map(walk).join("") + "\n";
|
|
1118
|
+
case "bulletList":
|
|
1119
|
+
case "orderedList":
|
|
1120
|
+
return (node.content || []).map(walk).join("\n");
|
|
1121
|
+
case "listItem":
|
|
1122
|
+
return "\u2022 " + (node.content || []).map(walk).join("");
|
|
1123
|
+
case "hardBreak":
|
|
1124
|
+
return "\n";
|
|
1125
|
+
default:
|
|
1126
|
+
if (Array.isArray(node.content)) return node.content.map(walk).join("");
|
|
1127
|
+
return "";
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
if (adf && Array.isArray(adf.content)) {
|
|
1131
|
+
return adf.content.map(walk).join("\n").trim();
|
|
1132
|
+
}
|
|
1133
|
+
} catch {
|
|
1134
|
+
}
|
|
1135
|
+
return "";
|
|
1136
|
+
}
|
|
1137
|
+
function formatIssueResponse(issue) {
|
|
1138
|
+
const { key, fields } = issue;
|
|
1139
|
+
const summary = fields?.summary || "No summary";
|
|
1140
|
+
const assigneeText = fields?.assignee ? `${fields.assignee.displayName} (${maybeRedactAccountId(fields.assignee.accountId)})` : "Unassigned";
|
|
1141
|
+
const reporterText = fields?.reporter ? `${fields.reporter.displayName} (${maybeRedactAccountId(fields.reporter.accountId)})` : "Unknown";
|
|
1142
|
+
const labelsArray = Array.isArray(fields?.labels) ? fields.labels : [];
|
|
1143
|
+
const labelsText = labelsArray.length > 0 ? labelsArray.join(", ") : "None";
|
|
1144
|
+
const componentsArray = Array.isArray(fields?.components) ? fields.components : [];
|
|
1145
|
+
const componentsText = componentsArray.length > 0 ? componentsArray.map((c) => c.name).join(", ") : "None";
|
|
1146
|
+
let description = fields?.description;
|
|
1147
|
+
if (description && typeof description === "object") {
|
|
1148
|
+
const parsed = adfToPlainText(description);
|
|
1149
|
+
description = parsed || "[rich text description omitted]";
|
|
1150
|
+
}
|
|
1151
|
+
if (!description || typeof description !== "string") {
|
|
1152
|
+
description = "No description provided";
|
|
1153
|
+
}
|
|
1154
|
+
const createdText = fields?.created ? new Date(fields.created).toISOString() : "N/A";
|
|
1155
|
+
const updatedText = fields?.updated ? new Date(fields.updated).toISOString() : "N/A";
|
|
1156
|
+
return {
|
|
1157
|
+
content: [
|
|
1158
|
+
{
|
|
1159
|
+
type: "text",
|
|
1160
|
+
text: `**${key}: ${summary}**
|
|
1161
|
+
|
|
1162
|
+
**Status:** ${fields?.status?.name || "Unknown"}
|
|
1163
|
+
**Priority:** ${fields?.priority?.name || "None"}
|
|
1164
|
+
**Assignee:** ${assigneeText}
|
|
1165
|
+
**Reporter:** ${reporterText}
|
|
1166
|
+
**Project:** ${fields?.project?.name || "Unknown"} (${fields?.project?.key || "N/A"})
|
|
1167
|
+
**Issue Type:** ${fields?.issuetype?.name || "Unknown"}
|
|
1168
|
+
**Labels:** ${labelsText}
|
|
1169
|
+
**Components:** ${componentsText}
|
|
1170
|
+
**Created:** ${createdText}
|
|
1171
|
+
**Updated:** ${updatedText}
|
|
1172
|
+
|
|
1173
|
+
**Description:**
|
|
1174
|
+
${description}`
|
|
1175
|
+
}
|
|
1176
|
+
]
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
function formatSearchResultsResponse(result) {
|
|
1180
|
+
if (result.issues.length === 0) {
|
|
1181
|
+
return {
|
|
1182
|
+
content: [
|
|
1183
|
+
{
|
|
1184
|
+
type: "text",
|
|
1185
|
+
text: "No issues found matching your search criteria."
|
|
1186
|
+
}
|
|
1187
|
+
]
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
const issuesList = result.issues.map((issue) => {
|
|
1191
|
+
const assignee = issue.fields.assignee?.displayName || "Unassigned";
|
|
1192
|
+
return `\u2022 **${issue.key}** - ${issue.fields.summary}
|
|
1193
|
+
Status: ${issue.fields.status.name} | Assignee: ${assignee} | Priority: ${issue.fields.priority?.name || "None"}`;
|
|
1194
|
+
}).join("\n\n");
|
|
1195
|
+
const paginationInfo = result.total > result.maxResults ? `
|
|
1196
|
+
|
|
1197
|
+
*Showing ${result.startAt + 1}-${Math.min(result.startAt + result.maxResults, result.total)} of ${result.total} results*` : "";
|
|
1198
|
+
return {
|
|
1199
|
+
content: [
|
|
1200
|
+
{
|
|
1201
|
+
type: "text",
|
|
1202
|
+
text: `Found ${result.total} issue(s):
|
|
1203
|
+
|
|
1204
|
+
${issuesList}${paginationInfo}`
|
|
1205
|
+
}
|
|
1206
|
+
]
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
function formatErrorResponse(error) {
|
|
1210
|
+
let errorMessage = "An unexpected error occurred.";
|
|
1211
|
+
if (typeof error === "string") {
|
|
1212
|
+
errorMessage = error;
|
|
1213
|
+
} else if (error instanceof Error) {
|
|
1214
|
+
errorMessage = error.message;
|
|
1215
|
+
} else if (error?.response?.data) {
|
|
1216
|
+
const jiraError = error.response.data;
|
|
1217
|
+
if (jiraError.errorMessages && jiraError.errorMessages.length > 0) {
|
|
1218
|
+
errorMessage = jiraError.errorMessages.join(", ");
|
|
1219
|
+
} else if (jiraError.errors) {
|
|
1220
|
+
errorMessage = Object.entries(jiraError.errors).map(([field, message]) => `${field}: ${message}`).join(", ");
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return {
|
|
1224
|
+
content: [
|
|
1225
|
+
{
|
|
1226
|
+
type: "text",
|
|
1227
|
+
text: `\u274C Error: ${errorMessage}`
|
|
1228
|
+
}
|
|
1229
|
+
]
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
function formatSuccessResponse(message) {
|
|
1233
|
+
return {
|
|
1234
|
+
content: [
|
|
1235
|
+
{
|
|
1236
|
+
type: "text",
|
|
1237
|
+
text: `\u2705 ${message}`
|
|
1238
|
+
}
|
|
1239
|
+
]
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
function formatUsersResponse(users) {
|
|
1243
|
+
if (users.length === 0) {
|
|
1244
|
+
return {
|
|
1245
|
+
content: [
|
|
1246
|
+
{
|
|
1247
|
+
type: "text",
|
|
1248
|
+
text: "No users found matching your search criteria."
|
|
1249
|
+
}
|
|
1250
|
+
]
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
const usersList = users.map(
|
|
1254
|
+
(user) => `\u2022 **${user.displayName}** (${maybeRedactAccountId(user.accountId)})
|
|
1255
|
+
Email: ${maybeRedactEmail(user.emailAddress)} | Active: ${user.active ? "Yes" : "No"} | Type: ${user.accountType}`
|
|
1256
|
+
).join("\n\n");
|
|
1257
|
+
return {
|
|
1258
|
+
content: [
|
|
1259
|
+
{
|
|
1260
|
+
type: "text",
|
|
1261
|
+
text: `Found ${users.length} user(s):
|
|
1262
|
+
|
|
1263
|
+
${usersList}`
|
|
1264
|
+
}
|
|
1265
|
+
]
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
function formatIssueTypesResponse(issueTypes) {
|
|
1269
|
+
if (issueTypes.length === 0) {
|
|
1270
|
+
return {
|
|
1271
|
+
content: [
|
|
1272
|
+
{
|
|
1273
|
+
type: "text",
|
|
1274
|
+
text: "No issue types found."
|
|
1275
|
+
}
|
|
1276
|
+
]
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
const typesList = issueTypes.map(
|
|
1280
|
+
(type) => `\u2022 **${type.name}** (ID: ${type.id})
|
|
1281
|
+
${type.description}
|
|
1282
|
+
Subtask: ${type.subtask ? "Yes" : "No"}`
|
|
1283
|
+
).join("\n\n");
|
|
1284
|
+
return {
|
|
1285
|
+
content: [
|
|
1286
|
+
{
|
|
1287
|
+
type: "text",
|
|
1288
|
+
text: `Found ${issueTypes.length} issue type(s):
|
|
1289
|
+
|
|
1290
|
+
${typesList}`
|
|
1291
|
+
}
|
|
1292
|
+
]
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function formatPrioritiesResponse(priorities) {
|
|
1296
|
+
if (priorities.length === 0) {
|
|
1297
|
+
return {
|
|
1298
|
+
content: [
|
|
1299
|
+
{
|
|
1300
|
+
type: "text",
|
|
1301
|
+
text: "No priorities found."
|
|
1302
|
+
}
|
|
1303
|
+
]
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
const prioritiesList = priorities.map((priority) => `\u2022 **${priority.name}** (ID: ${priority.id})
|
|
1307
|
+
${priority.description}`).join("\n\n");
|
|
1308
|
+
return {
|
|
1309
|
+
content: [
|
|
1310
|
+
{
|
|
1311
|
+
type: "text",
|
|
1312
|
+
text: `Found ${priorities.length} priority level(s):
|
|
1313
|
+
|
|
1314
|
+
${prioritiesList}`
|
|
1315
|
+
}
|
|
1316
|
+
]
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
function formatStatusesResponse(statuses) {
|
|
1320
|
+
if (statuses.length === 0) {
|
|
1321
|
+
return {
|
|
1322
|
+
content: [
|
|
1323
|
+
{
|
|
1324
|
+
type: "text",
|
|
1325
|
+
text: "No statuses found."
|
|
1326
|
+
}
|
|
1327
|
+
]
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
const statusesList = statuses.map(
|
|
1331
|
+
(status) => `\u2022 **${status.name}** (ID: ${status.id})
|
|
1332
|
+
${status.description}
|
|
1333
|
+
Category: ${status.statusCategory.name} (${status.statusCategory.key})`
|
|
1334
|
+
).join("\n\n");
|
|
1335
|
+
return {
|
|
1336
|
+
content: [
|
|
1337
|
+
{
|
|
1338
|
+
type: "text",
|
|
1339
|
+
text: `Found ${statuses.length} status(es):
|
|
1340
|
+
|
|
1341
|
+
${statusesList}`
|
|
1342
|
+
}
|
|
1343
|
+
]
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
function formatCommentResponse(comment) {
|
|
1347
|
+
const visibilityText = comment.visibility ? `
|
|
1348
|
+
**Visibility:** ${comment.visibility.type} - ${comment.visibility.value}` : "";
|
|
1349
|
+
return {
|
|
1350
|
+
content: [
|
|
1351
|
+
{
|
|
1352
|
+
type: "text",
|
|
1353
|
+
text: `**Comment added successfully**
|
|
1354
|
+
|
|
1355
|
+
**Author:** ${comment.author.displayName}
|
|
1356
|
+
**Created:** ${new Date(comment.created).toISOString()}${visibilityText}
|
|
1357
|
+
|
|
1358
|
+
**Content:**
|
|
1359
|
+
${comment.body}`
|
|
1360
|
+
}
|
|
1361
|
+
]
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
function formatProjectDetailsResponse(project) {
|
|
1365
|
+
const leadText = project.lead ? `${project.lead.displayName} (${project.lead.accountId})` : "No lead assigned";
|
|
1366
|
+
const componentsText = project.components.length > 0 ? project.components.map((c) => c.name).join(", ") : "None";
|
|
1367
|
+
const versionsText = project.versions.length > 0 ? project.versions.map((v) => v.name).join(", ") : "None";
|
|
1368
|
+
const issueTypesText = project.issueTypes.length > 0 ? project.issueTypes.map((t) => `${t.name}${t.subtask ? " (Subtask)" : ""}`).join(", ") : "None";
|
|
1369
|
+
const rolesText = Object.keys(project.roles).length > 0 ? Object.keys(project.roles).join(", ") : "None";
|
|
1370
|
+
const insightText = project.insight ? `
|
|
1371
|
+
|
|
1372
|
+
**Project Insights:**
|
|
1373
|
+
Total Issues: ${project.insight.totalIssueCount}
|
|
1374
|
+
Last Updated: ${new Date(project.insight.lastIssueUpdateTime).toISOString()}` : "";
|
|
1375
|
+
return {
|
|
1376
|
+
content: [
|
|
1377
|
+
{
|
|
1378
|
+
type: "text",
|
|
1379
|
+
text: `**${project.key}: ${project.name}**
|
|
1380
|
+
|
|
1381
|
+
**Description:** ${project.description || "No description"}
|
|
1382
|
+
**Type:** ${project.projectTypeKey}
|
|
1383
|
+
**Private:** ${project.isPrivate ? "Yes" : "No"}
|
|
1384
|
+
**Lead:** ${leadText}
|
|
1385
|
+
|
|
1386
|
+
**Components:** ${componentsText}
|
|
1387
|
+
**Versions:** ${versionsText}
|
|
1388
|
+
**Issue Types:** ${issueTypesText}
|
|
1389
|
+
**Roles:** ${rolesText}${insightText}`
|
|
1390
|
+
}
|
|
1391
|
+
]
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/utils/error-handler.ts
|
|
1396
|
+
var log2 = createLogger("error-handler");
|
|
1397
|
+
var JiraApiError = class extends Error {
|
|
1398
|
+
constructor(message, statusCode, jiraErrors, originalError) {
|
|
1399
|
+
super(message);
|
|
1400
|
+
this.statusCode = statusCode;
|
|
1401
|
+
this.jiraErrors = jiraErrors;
|
|
1402
|
+
this.originalError = originalError;
|
|
1403
|
+
this.name = "JiraApiError";
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
function handleError(error) {
|
|
1407
|
+
log2.error("Tool error:", error);
|
|
1408
|
+
if (typeof error === "string") {
|
|
1409
|
+
return formatErrorResponse(error);
|
|
1410
|
+
}
|
|
1411
|
+
if (error instanceof JiraApiError) {
|
|
1412
|
+
return formatErrorResponse(error.message);
|
|
1413
|
+
}
|
|
1414
|
+
if (error?.response?.status === 401) {
|
|
1415
|
+
return formatErrorResponse(ERROR_MESSAGES.INVALID_CREDENTIALS);
|
|
1416
|
+
}
|
|
1417
|
+
if (error?.response?.status === 404) {
|
|
1418
|
+
return formatErrorResponse("Resource not found or insufficient permissions");
|
|
1419
|
+
}
|
|
1420
|
+
if (error?.response?.status === 429) {
|
|
1421
|
+
return formatErrorResponse(ERROR_MESSAGES.RATE_LIMIT_EXCEEDED);
|
|
1422
|
+
}
|
|
1423
|
+
if (!error?.response && error?.code === "ECONNREFUSED") {
|
|
1424
|
+
return formatErrorResponse(ERROR_MESSAGES.NETWORK_ERROR);
|
|
1425
|
+
}
|
|
1426
|
+
if (error?.message?.includes("validation")) {
|
|
1427
|
+
return formatErrorResponse(error.message);
|
|
1428
|
+
}
|
|
1429
|
+
if (error?.response?.data) {
|
|
1430
|
+
const jiraError = error.response.data;
|
|
1431
|
+
if (jiraError.errorMessages && jiraError.errorMessages.length > 0) {
|
|
1432
|
+
return formatErrorResponse(jiraError.errorMessages.join(", "));
|
|
1433
|
+
}
|
|
1434
|
+
if (jiraError.errors) {
|
|
1435
|
+
const errorDetails = Object.entries(jiraError.errors).map(([field, message2]) => `${field}: ${message2}`).join(", ");
|
|
1436
|
+
return formatErrorResponse(errorDetails);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
const message = error?.message || "An unexpected error occurred";
|
|
1440
|
+
return formatErrorResponse(message);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// src/tools/get-visible-projects.ts
|
|
1444
|
+
var log3 = createLogger("tool:get-visible-projects");
|
|
1445
|
+
var getVisibleProjectsTool = {
|
|
1446
|
+
name: TOOL_NAMES.GET_VISIBLE_PROJECTS,
|
|
1447
|
+
description: "Retrieves all projects accessible to the authenticated user. Returns project keys, names, descriptions, and basic metadata.",
|
|
1448
|
+
inputSchema: {
|
|
1449
|
+
type: "object",
|
|
1450
|
+
properties: {
|
|
1451
|
+
expand: {
|
|
1452
|
+
type: "array",
|
|
1453
|
+
items: { type: "string" },
|
|
1454
|
+
description: 'Additional project details to include (e.g., ["description", "lead", "issueTypes"])',
|
|
1455
|
+
default: []
|
|
1456
|
+
},
|
|
1457
|
+
recent: {
|
|
1458
|
+
type: "number",
|
|
1459
|
+
description: "Limit results to recently accessed projects (max number)",
|
|
1460
|
+
minimum: 1,
|
|
1461
|
+
maximum: 100
|
|
1462
|
+
}
|
|
1463
|
+
},
|
|
1464
|
+
required: []
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
async function handleGetVisibleProjects(input) {
|
|
1468
|
+
try {
|
|
1469
|
+
const validated = validateInput(GetVisibleProjectsInputSchema, input);
|
|
1470
|
+
log3.info("Getting visible projects...");
|
|
1471
|
+
const getParams = {};
|
|
1472
|
+
if (validated.expand !== void 0) getParams.expand = validated.expand;
|
|
1473
|
+
if (validated.recent !== void 0) getParams.recent = validated.recent;
|
|
1474
|
+
const projects = await getVisibleProjects(getParams);
|
|
1475
|
+
log3.info(`Found ${projects.length} project(s)`);
|
|
1476
|
+
return formatProjectsResponse(projects);
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
log3.error("Error in handleGetVisibleProjects:", error);
|
|
1479
|
+
return handleError(error);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// src/tools/get-issue.ts
|
|
1484
|
+
var log4 = createLogger("tool:get-issue");
|
|
1485
|
+
var getIssueTool = {
|
|
1486
|
+
name: TOOL_NAMES.GET_ISSUE,
|
|
1487
|
+
description: 'Retrieve details for a specific Jira issue by key or URL. Use this when the user mentions an issue like "PAYWALL-943" or pastes a Jira link (e.g., https://your.atlassian.net/browse/PAYWALL-943). Returns status, assignee, priority, project, type, labels, components, timestamps, and description.',
|
|
1488
|
+
inputSchema: {
|
|
1489
|
+
type: "object",
|
|
1490
|
+
properties: {
|
|
1491
|
+
issueKey: {
|
|
1492
|
+
type: "string",
|
|
1493
|
+
description: "Issue key or full Jira URL (e.g., PROJECT-123 or https://your.atlassian.net/browse/PROJECT-123)"
|
|
1494
|
+
},
|
|
1495
|
+
expand: {
|
|
1496
|
+
type: "array",
|
|
1497
|
+
items: { type: "string" },
|
|
1498
|
+
description: 'Additional issue details to include (e.g., ["comments", "attachments", "changelog"])',
|
|
1499
|
+
default: []
|
|
1500
|
+
},
|
|
1501
|
+
fields: {
|
|
1502
|
+
type: "array",
|
|
1503
|
+
items: { type: "string" },
|
|
1504
|
+
description: 'Specific fields to retrieve (e.g., ["summary", "status", "assignee"])'
|
|
1505
|
+
}
|
|
1506
|
+
},
|
|
1507
|
+
required: ["issueKey"]
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
async function handleGetIssue(input) {
|
|
1511
|
+
try {
|
|
1512
|
+
const validated = validateInput(GetIssueInputSchema, input);
|
|
1513
|
+
const key = (validated.issueKey || "").trim();
|
|
1514
|
+
log4.info(`Getting issue ${key}...`);
|
|
1515
|
+
const getParams = {};
|
|
1516
|
+
if (validated.expand !== void 0) getParams.expand = validated.expand;
|
|
1517
|
+
if (validated.fields !== void 0) getParams.fields = validated.fields;
|
|
1518
|
+
const issue = await getIssue(key, getParams);
|
|
1519
|
+
log4.info(`Retrieved issue ${issue.key}`);
|
|
1520
|
+
return formatIssueResponse(issue);
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
log4.error("Error in handleGetIssue:", error);
|
|
1523
|
+
return handleError(error);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// src/tools/search-issues.ts
|
|
1528
|
+
var log5 = createLogger("tool:search-issues");
|
|
1529
|
+
var searchIssuesTool = {
|
|
1530
|
+
name: TOOL_NAMES.SEARCH_ISSUES,
|
|
1531
|
+
description: 'Search for Jira issues using JQL. Supports complex queries with pagination and field selection. Examples: "project = PROJECT AND status = Open", "assignee = currentUser()".',
|
|
1532
|
+
inputSchema: {
|
|
1533
|
+
type: "object",
|
|
1534
|
+
properties: {
|
|
1535
|
+
jql: {
|
|
1536
|
+
type: "string",
|
|
1537
|
+
description: 'JQL query string (e.g., "project = PROJECT AND status = Open")'
|
|
1538
|
+
},
|
|
1539
|
+
startAt: {
|
|
1540
|
+
type: "number",
|
|
1541
|
+
description: "Index of first result to return (for pagination)",
|
|
1542
|
+
minimum: 0,
|
|
1543
|
+
default: 0
|
|
1544
|
+
},
|
|
1545
|
+
maxResults: {
|
|
1546
|
+
type: "number",
|
|
1547
|
+
description: "Maximum number of results to return",
|
|
1548
|
+
minimum: 1,
|
|
1549
|
+
maximum: 100,
|
|
1550
|
+
default: 50
|
|
1551
|
+
},
|
|
1552
|
+
fields: {
|
|
1553
|
+
type: "array",
|
|
1554
|
+
items: { type: "string" },
|
|
1555
|
+
description: "Specific fields to retrieve for each issue"
|
|
1556
|
+
},
|
|
1557
|
+
expand: {
|
|
1558
|
+
type: "array",
|
|
1559
|
+
items: { type: "string" },
|
|
1560
|
+
description: "Additional details to include for each issue",
|
|
1561
|
+
default: []
|
|
1562
|
+
}
|
|
1563
|
+
},
|
|
1564
|
+
required: ["jql"]
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
async function handleSearchIssues(input) {
|
|
1568
|
+
try {
|
|
1569
|
+
const validated = validateInput(SearchIssuesInputSchema, input);
|
|
1570
|
+
log5.info(`Searching issues with JQL: "${validated.jql}"...`);
|
|
1571
|
+
const searchParams = {
|
|
1572
|
+
jql: validated.jql
|
|
1573
|
+
};
|
|
1574
|
+
if (validated.startAt !== void 0) searchParams.startAt = validated.startAt;
|
|
1575
|
+
if (validated.maxResults !== void 0) searchParams.maxResults = validated.maxResults;
|
|
1576
|
+
if (validated.fields !== void 0) searchParams.fields = validated.fields;
|
|
1577
|
+
if (validated.expand !== void 0) searchParams.expand = validated.expand;
|
|
1578
|
+
const result = await searchIssues(searchParams);
|
|
1579
|
+
log5.info(`Found ${result.total} issue(s), showing ${result.issues.length}`);
|
|
1580
|
+
return formatSearchResultsResponse(result);
|
|
1581
|
+
} catch (error) {
|
|
1582
|
+
log5.error("Error in handleSearchIssues:", error);
|
|
1583
|
+
return handleError(error);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// src/tools/get-my-issues.ts
|
|
1588
|
+
var log6 = createLogger("tool:get-my-issues");
|
|
1589
|
+
var getMyIssuesTool = {
|
|
1590
|
+
name: TOOL_NAMES.GET_MY_ISSUES,
|
|
1591
|
+
description: "Retrieves issues assigned to current user, sorted by most recently updated first. Supports pagination and field selection.",
|
|
1592
|
+
inputSchema: {
|
|
1593
|
+
type: "object",
|
|
1594
|
+
properties: {
|
|
1595
|
+
startAt: {
|
|
1596
|
+
type: "number",
|
|
1597
|
+
description: "Index of first result to return (for pagination)",
|
|
1598
|
+
minimum: 0,
|
|
1599
|
+
default: 0
|
|
1600
|
+
},
|
|
1601
|
+
maxResults: {
|
|
1602
|
+
type: "number",
|
|
1603
|
+
description: "Maximum number of results to return",
|
|
1604
|
+
minimum: 1,
|
|
1605
|
+
maximum: 100,
|
|
1606
|
+
default: 50
|
|
1607
|
+
},
|
|
1608
|
+
fields: {
|
|
1609
|
+
type: "array",
|
|
1610
|
+
items: { type: "string" },
|
|
1611
|
+
description: "Specific fields to retrieve for each issue"
|
|
1612
|
+
},
|
|
1613
|
+
expand: {
|
|
1614
|
+
type: "array",
|
|
1615
|
+
items: { type: "string" },
|
|
1616
|
+
description: "Additional details to include for each issue",
|
|
1617
|
+
default: []
|
|
1618
|
+
}
|
|
1619
|
+
},
|
|
1620
|
+
required: []
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
async function handleGetMyIssues(input) {
|
|
1624
|
+
try {
|
|
1625
|
+
const validated = validateInput(GetMyIssuesInputSchema, input);
|
|
1626
|
+
log6.info("Getting issues assigned to current user...");
|
|
1627
|
+
const getParams = {};
|
|
1628
|
+
if (validated.startAt !== void 0) getParams.startAt = validated.startAt;
|
|
1629
|
+
if (validated.maxResults !== void 0) getParams.maxResults = validated.maxResults;
|
|
1630
|
+
if (validated.fields !== void 0) getParams.fields = validated.fields;
|
|
1631
|
+
if (validated.expand !== void 0) getParams.expand = validated.expand;
|
|
1632
|
+
const result = await getMyIssues(getParams);
|
|
1633
|
+
log6.info(`Found ${result.total} issue(s) assigned to you, showing ${result.issues.length}`);
|
|
1634
|
+
return formatSearchResultsResponse(result);
|
|
1635
|
+
} catch (error) {
|
|
1636
|
+
log6.error("Error in handleGetMyIssues:", error);
|
|
1637
|
+
return handleError(error);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// src/tools/get-issue-types.ts
|
|
1642
|
+
var log7 = createLogger("tool:get-issue-types");
|
|
1643
|
+
var getIssueTypesTool = {
|
|
1644
|
+
name: TOOL_NAMES.GET_ISSUE_TYPES,
|
|
1645
|
+
description: "Retrieves available issue types. Can get global issue types or project-specific issue types including regular issues and subtasks (Bug, Story, Task, Epic, etc.).",
|
|
1646
|
+
inputSchema: {
|
|
1647
|
+
type: "object",
|
|
1648
|
+
properties: {
|
|
1649
|
+
projectKey: {
|
|
1650
|
+
type: "string",
|
|
1651
|
+
description: "Project key to get issue types for specific project (optional - if not provided, returns global issue types)"
|
|
1652
|
+
}
|
|
1653
|
+
},
|
|
1654
|
+
required: []
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
async function handleGetIssueTypes(input) {
|
|
1658
|
+
try {
|
|
1659
|
+
const validated = validateInput(GetIssueTypesInputSchema, input);
|
|
1660
|
+
if (validated.projectKey) {
|
|
1661
|
+
log7.info(`Getting issue types for project ${validated.projectKey}...`);
|
|
1662
|
+
} else {
|
|
1663
|
+
log7.info("Getting global issue types...");
|
|
1664
|
+
}
|
|
1665
|
+
const issueTypes = await getIssueTypes(validated.projectKey);
|
|
1666
|
+
log7.info(`Found ${issueTypes.length} issue type(s)`);
|
|
1667
|
+
return formatIssueTypesResponse(issueTypes);
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
log7.error("Error in handleGetIssueTypes:", error);
|
|
1670
|
+
return handleError(error);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
// src/tools/get-users.ts
|
|
1675
|
+
var log8 = createLogger("tool:get-users");
|
|
1676
|
+
var getUsersTool = {
|
|
1677
|
+
name: TOOL_NAMES.GET_USERS,
|
|
1678
|
+
description: "Search for users by name, email, username, or account ID. Returns display name, email, account status, and account type. Supports pagination.",
|
|
1679
|
+
inputSchema: {
|
|
1680
|
+
type: "object",
|
|
1681
|
+
properties: {
|
|
1682
|
+
query: {
|
|
1683
|
+
type: "string",
|
|
1684
|
+
description: "Search query for user name or email (partial matches supported)"
|
|
1685
|
+
},
|
|
1686
|
+
username: {
|
|
1687
|
+
type: "string",
|
|
1688
|
+
description: "Specific username to search for"
|
|
1689
|
+
},
|
|
1690
|
+
accountId: {
|
|
1691
|
+
type: "string",
|
|
1692
|
+
description: "Specific account ID to search for"
|
|
1693
|
+
},
|
|
1694
|
+
startAt: {
|
|
1695
|
+
type: "number",
|
|
1696
|
+
description: "Index of first result to return (for pagination)",
|
|
1697
|
+
minimum: 0,
|
|
1698
|
+
default: 0
|
|
1699
|
+
},
|
|
1700
|
+
maxResults: {
|
|
1701
|
+
type: "number",
|
|
1702
|
+
description: "Maximum number of results to return",
|
|
1703
|
+
minimum: 1,
|
|
1704
|
+
maximum: 50,
|
|
1705
|
+
default: 50
|
|
1706
|
+
}
|
|
1707
|
+
},
|
|
1708
|
+
required: []
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
async function handleGetUsers(input) {
|
|
1712
|
+
try {
|
|
1713
|
+
const validated = validateInput(GetUsersInputSchema, input);
|
|
1714
|
+
if (validated.query) {
|
|
1715
|
+
log8.info(`Searching users with query: "${validated.query}"...`);
|
|
1716
|
+
} else if (validated.username) {
|
|
1717
|
+
log8.info(`Searching for username: "${validated.username}"...`);
|
|
1718
|
+
} else if (validated.accountId) {
|
|
1719
|
+
log8.info(`Searching for account ID: "${validated.accountId}"...`);
|
|
1720
|
+
} else {
|
|
1721
|
+
log8.info("Getting all users...");
|
|
1722
|
+
}
|
|
1723
|
+
const getParams = {};
|
|
1724
|
+
if (validated.query !== void 0) getParams.query = validated.query;
|
|
1725
|
+
if (validated.username !== void 0) getParams.username = validated.username;
|
|
1726
|
+
if (validated.accountId !== void 0) getParams.accountId = validated.accountId;
|
|
1727
|
+
if (validated.startAt !== void 0) getParams.startAt = validated.startAt;
|
|
1728
|
+
if (validated.maxResults !== void 0) getParams.maxResults = validated.maxResults;
|
|
1729
|
+
const users = await getUsers(getParams);
|
|
1730
|
+
log8.info(`Found ${users.length} user(s)`);
|
|
1731
|
+
return formatUsersResponse(users);
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
log8.error("Error in handleGetUsers:", error);
|
|
1734
|
+
return handleError(error);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// src/tools/get-priorities.ts
|
|
1739
|
+
var log9 = createLogger("tool:get-priorities");
|
|
1740
|
+
var getPrioritiesTool = {
|
|
1741
|
+
name: TOOL_NAMES.GET_PRIORITIES,
|
|
1742
|
+
description: "Retrieves available priorities (e.g., Highest, High, Medium, Low, Lowest). Returns IDs, names, and descriptions.",
|
|
1743
|
+
inputSchema: {
|
|
1744
|
+
type: "object",
|
|
1745
|
+
properties: {},
|
|
1746
|
+
required: []
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
async function handleGetPriorities(input) {
|
|
1750
|
+
try {
|
|
1751
|
+
validateInput(GetPrioritiesInputSchema, input);
|
|
1752
|
+
log9.info("Getting all priority levels...");
|
|
1753
|
+
const priorities = await getPriorities();
|
|
1754
|
+
log9.info(`Found ${priorities.length} priority level(s)`);
|
|
1755
|
+
return formatPrioritiesResponse(priorities);
|
|
1756
|
+
} catch (error) {
|
|
1757
|
+
log9.error("Error in handleGetPriorities:", error);
|
|
1758
|
+
return handleError(error);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// src/tools/get-statuses.ts
|
|
1763
|
+
var log10 = createLogger("tool:get-statuses");
|
|
1764
|
+
var getStatusesTool = {
|
|
1765
|
+
name: TOOL_NAMES.GET_STATUSES,
|
|
1766
|
+
description: "Retrieves available statuses (global or project-specific, e.g., To Do, In Progress, Done). Returns status categories and workflow information.",
|
|
1767
|
+
inputSchema: {
|
|
1768
|
+
type: "object",
|
|
1769
|
+
properties: {
|
|
1770
|
+
projectKey: {
|
|
1771
|
+
type: "string",
|
|
1772
|
+
description: "Project key to get statuses for specific project (optional)"
|
|
1773
|
+
},
|
|
1774
|
+
issueTypeId: {
|
|
1775
|
+
type: "string",
|
|
1776
|
+
description: "Issue type ID to get statuses for specific issue type (requires projectKey)"
|
|
1777
|
+
}
|
|
1778
|
+
},
|
|
1779
|
+
required: []
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
async function handleGetStatuses(input) {
|
|
1783
|
+
try {
|
|
1784
|
+
const validated = validateInput(GetStatusesInputSchema, input);
|
|
1785
|
+
if (validated.projectKey && validated.issueTypeId) {
|
|
1786
|
+
log10.info(
|
|
1787
|
+
`Getting statuses for project ${validated.projectKey} and issue type ${validated.issueTypeId}...`
|
|
1788
|
+
);
|
|
1789
|
+
} else if (validated.projectKey) {
|
|
1790
|
+
log10.info(`Getting statuses for project ${validated.projectKey}...`);
|
|
1791
|
+
} else {
|
|
1792
|
+
log10.info("Getting global statuses...");
|
|
1793
|
+
}
|
|
1794
|
+
const getParams = {};
|
|
1795
|
+
if (validated.projectKey !== void 0) getParams.projectKey = validated.projectKey;
|
|
1796
|
+
if (validated.issueTypeId !== void 0) getParams.issueTypeId = validated.issueTypeId;
|
|
1797
|
+
const statuses = await getStatuses(getParams);
|
|
1798
|
+
log10.info(`Found ${statuses.length} status(es)`);
|
|
1799
|
+
return formatStatusesResponse(statuses);
|
|
1800
|
+
} catch (error) {
|
|
1801
|
+
log10.error("Error in handleGetStatuses:", error);
|
|
1802
|
+
return handleError(error);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// src/tools/create-issue.ts
|
|
1807
|
+
var log11 = createLogger("tool:create-issue");
|
|
1808
|
+
var createIssueTool = {
|
|
1809
|
+
name: TOOL_NAMES.CREATE_ISSUE,
|
|
1810
|
+
description: "Creates a new Jira issue in the specified project. Supports setting issue type, priority, assignee, labels, and components. Returns the created issue with all details.",
|
|
1811
|
+
inputSchema: {
|
|
1812
|
+
type: "object",
|
|
1813
|
+
properties: {
|
|
1814
|
+
projectKey: {
|
|
1815
|
+
type: "string",
|
|
1816
|
+
description: "Project key where the issue will be created"
|
|
1817
|
+
},
|
|
1818
|
+
summary: {
|
|
1819
|
+
type: "string",
|
|
1820
|
+
description: "Issue summary/title",
|
|
1821
|
+
minLength: 1
|
|
1822
|
+
},
|
|
1823
|
+
description: {
|
|
1824
|
+
type: "string",
|
|
1825
|
+
description: "Detailed issue description (optional)"
|
|
1826
|
+
},
|
|
1827
|
+
issueType: {
|
|
1828
|
+
type: "string",
|
|
1829
|
+
description: "Issue type name (e.g., Bug, Story, Task, Epic)"
|
|
1830
|
+
},
|
|
1831
|
+
priority: {
|
|
1832
|
+
type: "string",
|
|
1833
|
+
description: "Issue priority name (e.g., High, Medium, Low) - optional"
|
|
1834
|
+
},
|
|
1835
|
+
assignee: {
|
|
1836
|
+
type: "string",
|
|
1837
|
+
description: "Assignee account ID (optional)"
|
|
1838
|
+
},
|
|
1839
|
+
labels: {
|
|
1840
|
+
type: "array",
|
|
1841
|
+
items: { type: "string" },
|
|
1842
|
+
description: "Issue labels (optional)",
|
|
1843
|
+
default: []
|
|
1844
|
+
},
|
|
1845
|
+
components: {
|
|
1846
|
+
type: "array",
|
|
1847
|
+
items: { type: "string" },
|
|
1848
|
+
description: "Component names (optional)",
|
|
1849
|
+
default: []
|
|
1850
|
+
},
|
|
1851
|
+
returnIssue: {
|
|
1852
|
+
type: "boolean",
|
|
1853
|
+
description: "If false, returns only the issue key without fetching full details",
|
|
1854
|
+
default: true
|
|
1855
|
+
}
|
|
1856
|
+
},
|
|
1857
|
+
required: ["projectKey", "summary", "issueType"]
|
|
1858
|
+
}
|
|
1859
|
+
};
|
|
1860
|
+
async function handleCreateIssue(input) {
|
|
1861
|
+
try {
|
|
1862
|
+
const validated = validateInput(CreateIssueInputSchema, input);
|
|
1863
|
+
log11.info(`Creating issue in project ${validated.projectKey}...`);
|
|
1864
|
+
const createParams = {
|
|
1865
|
+
projectKey: validated.projectKey,
|
|
1866
|
+
summary: validated.summary,
|
|
1867
|
+
issueType: validated.issueType
|
|
1868
|
+
};
|
|
1869
|
+
if (validated.description !== void 0) createParams.description = validated.description;
|
|
1870
|
+
if (validated.priority !== void 0) createParams.priority = validated.priority;
|
|
1871
|
+
if (validated.assignee !== void 0) createParams.assignee = validated.assignee;
|
|
1872
|
+
if (validated.labels !== void 0) createParams.labels = validated.labels;
|
|
1873
|
+
if (validated.components !== void 0) createParams.components = validated.components;
|
|
1874
|
+
let issueOrKey;
|
|
1875
|
+
if (validated.returnIssue === false) {
|
|
1876
|
+
issueOrKey = await createIssue(createParams, { returnIssue: false });
|
|
1877
|
+
} else {
|
|
1878
|
+
issueOrKey = await createIssue(createParams);
|
|
1879
|
+
}
|
|
1880
|
+
if (validated.returnIssue === false) {
|
|
1881
|
+
const key = typeof issueOrKey === "string" ? issueOrKey : issueOrKey.key;
|
|
1882
|
+
log11.info(`Created issue ${key}`);
|
|
1883
|
+
return formatSuccessResponse(`Issue created: ${key}`);
|
|
1884
|
+
}
|
|
1885
|
+
const issue = issueOrKey;
|
|
1886
|
+
log11.info(`Created issue ${issue.key}`);
|
|
1887
|
+
return formatIssueResponse(issue);
|
|
1888
|
+
} catch (error) {
|
|
1889
|
+
log11.error("Error in handleCreateIssue:", error);
|
|
1890
|
+
return handleError(error);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
// src/tools/update-issue.ts
|
|
1895
|
+
var log12 = createLogger("tool:update-issue");
|
|
1896
|
+
var updateIssueTool = {
|
|
1897
|
+
name: TOOL_NAMES.UPDATE_ISSUE,
|
|
1898
|
+
description: "Updates an existing Jira issue by its key. Supports updating summary, description, priority, assignee, labels, and components. Only specified fields will be updated.",
|
|
1899
|
+
inputSchema: {
|
|
1900
|
+
type: "object",
|
|
1901
|
+
properties: {
|
|
1902
|
+
issueKey: {
|
|
1903
|
+
type: "string",
|
|
1904
|
+
description: "Issue key to update (e.g., PROJECT-123)"
|
|
1905
|
+
},
|
|
1906
|
+
summary: {
|
|
1907
|
+
type: "string",
|
|
1908
|
+
description: "New issue summary/title (optional)"
|
|
1909
|
+
},
|
|
1910
|
+
description: {
|
|
1911
|
+
type: "string",
|
|
1912
|
+
description: "New issue description (optional)"
|
|
1913
|
+
},
|
|
1914
|
+
priority: {
|
|
1915
|
+
type: "string",
|
|
1916
|
+
description: "New priority name (e.g., High, Medium, Low) - optional"
|
|
1917
|
+
},
|
|
1918
|
+
assignee: {
|
|
1919
|
+
type: "string",
|
|
1920
|
+
description: "New assignee account ID (optional, use null string to unassign)"
|
|
1921
|
+
},
|
|
1922
|
+
labels: {
|
|
1923
|
+
type: "array",
|
|
1924
|
+
items: { type: "string" },
|
|
1925
|
+
description: "New labels array (replaces existing labels) - optional"
|
|
1926
|
+
},
|
|
1927
|
+
components: {
|
|
1928
|
+
type: "array",
|
|
1929
|
+
items: { type: "string" },
|
|
1930
|
+
description: "New components array (replaces existing components) - optional"
|
|
1931
|
+
},
|
|
1932
|
+
returnIssue: {
|
|
1933
|
+
type: "boolean",
|
|
1934
|
+
description: "If false, returns a success message without fetching the updated issue",
|
|
1935
|
+
default: true
|
|
1936
|
+
}
|
|
1937
|
+
},
|
|
1938
|
+
required: ["issueKey"]
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
async function handleUpdateIssue(input) {
|
|
1942
|
+
try {
|
|
1943
|
+
const validated = validateInput(UpdateIssueInputSchema, input);
|
|
1944
|
+
log12.info(`Updating issue ${validated.issueKey}...`);
|
|
1945
|
+
const updateParams = {};
|
|
1946
|
+
if (validated.summary !== void 0) updateParams.summary = validated.summary;
|
|
1947
|
+
if (validated.description !== void 0) updateParams.description = validated.description;
|
|
1948
|
+
if (validated.priority !== void 0) updateParams.priority = validated.priority;
|
|
1949
|
+
if (validated.assignee !== void 0) updateParams.assignee = validated.assignee;
|
|
1950
|
+
if (validated.labels !== void 0) updateParams.labels = validated.labels;
|
|
1951
|
+
if (validated.components !== void 0) updateParams.components = validated.components;
|
|
1952
|
+
await updateIssue(validated.issueKey, updateParams);
|
|
1953
|
+
if (validated.returnIssue === false) {
|
|
1954
|
+
log12.info(`Updated issue ${validated.issueKey}`);
|
|
1955
|
+
return formatSuccessResponse(`Issue ${validated.issueKey} updated successfully`);
|
|
1956
|
+
}
|
|
1957
|
+
const updatedIssue = await getIssue(validated.issueKey);
|
|
1958
|
+
log12.info(`Updated issue ${updatedIssue.key}`);
|
|
1959
|
+
return formatIssueResponse(updatedIssue);
|
|
1960
|
+
} catch (error) {
|
|
1961
|
+
log12.error("Error in handleUpdateIssue:", error);
|
|
1962
|
+
return handleError(error);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/tools/add-comment.ts
|
|
1967
|
+
var log13 = createLogger("tool:add-comment");
|
|
1968
|
+
var addCommentTool = {
|
|
1969
|
+
name: TOOL_NAMES.ADD_COMMENT,
|
|
1970
|
+
description: "Adds a comment to an issue. Supports visibility restrictions for groups or roles. Returns the created comment with author details and timestamp.",
|
|
1971
|
+
inputSchema: {
|
|
1972
|
+
type: "object",
|
|
1973
|
+
properties: {
|
|
1974
|
+
issueKey: {
|
|
1975
|
+
type: "string",
|
|
1976
|
+
description: "Issue key to add comment to (e.g., PROJECT-123)"
|
|
1977
|
+
},
|
|
1978
|
+
body: {
|
|
1979
|
+
type: "string",
|
|
1980
|
+
description: "Comment body text",
|
|
1981
|
+
minLength: 1
|
|
1982
|
+
},
|
|
1983
|
+
visibility: {
|
|
1984
|
+
type: "object",
|
|
1985
|
+
description: "Comment visibility restrictions (optional)",
|
|
1986
|
+
properties: {
|
|
1987
|
+
type: {
|
|
1988
|
+
type: "string",
|
|
1989
|
+
enum: ["group", "role"],
|
|
1990
|
+
description: 'Visibility type - either "group" or "role"'
|
|
1991
|
+
},
|
|
1992
|
+
value: {
|
|
1993
|
+
type: "string",
|
|
1994
|
+
description: "Group name or role name for visibility restriction"
|
|
1995
|
+
}
|
|
1996
|
+
},
|
|
1997
|
+
required: ["type", "value"]
|
|
1998
|
+
}
|
|
1999
|
+
},
|
|
2000
|
+
required: ["issueKey", "body"]
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
2003
|
+
async function handleAddComment(input) {
|
|
2004
|
+
try {
|
|
2005
|
+
const validated = validateInput(AddCommentInputSchema, input);
|
|
2006
|
+
log13.info(`Adding comment to issue ${validated.issueKey}...`);
|
|
2007
|
+
const comment = await addComment(validated.issueKey, validated.body, validated.visibility);
|
|
2008
|
+
log13.info(`Added comment to ${validated.issueKey}`);
|
|
2009
|
+
return formatCommentResponse(comment);
|
|
2010
|
+
} catch (error) {
|
|
2011
|
+
log13.error("Error in handleAddComment:", error);
|
|
2012
|
+
return handleError(error);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// src/tools/get-project-info.ts
|
|
2017
|
+
var log14 = createLogger("tool:get-project-info");
|
|
2018
|
+
var getProjectInfoTool = {
|
|
2019
|
+
name: TOOL_NAMES.GET_PROJECT_INFO,
|
|
2020
|
+
description: "Retrieves detailed information about a project (components, versions, issue types, roles, insights). More comprehensive than the basic project list.",
|
|
2021
|
+
inputSchema: {
|
|
2022
|
+
type: "object",
|
|
2023
|
+
properties: {
|
|
2024
|
+
projectKey: {
|
|
2025
|
+
type: "string",
|
|
2026
|
+
description: "Project key to get detailed information for"
|
|
2027
|
+
},
|
|
2028
|
+
expand: {
|
|
2029
|
+
type: "array",
|
|
2030
|
+
items: { type: "string" },
|
|
2031
|
+
description: 'Additional project details to include (e.g., ["description", "lead", "issueTypes", "versions"])',
|
|
2032
|
+
default: []
|
|
2033
|
+
}
|
|
2034
|
+
},
|
|
2035
|
+
required: ["projectKey"]
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
async function handleGetProjectInfo(input) {
|
|
2039
|
+
try {
|
|
2040
|
+
const validated = validateInput(GetProjectInfoInputSchema, input);
|
|
2041
|
+
log14.info(`Getting detailed project information for ${validated.projectKey}...`);
|
|
2042
|
+
const project = await getProjectDetails(validated.projectKey, validated.expand);
|
|
2043
|
+
log14.info(`Retrieved project details for ${project.key}`);
|
|
2044
|
+
return formatProjectDetailsResponse(project);
|
|
2045
|
+
} catch (error) {
|
|
2046
|
+
log14.error("Error in handleGetProjectInfo:", error);
|
|
2047
|
+
return handleError(error);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
// src/tools/create-subtask.ts
|
|
2052
|
+
var log15 = createLogger("tool:create-subtask");
|
|
2053
|
+
var createSubtaskTool = {
|
|
2054
|
+
name: TOOL_NAMES.CREATE_SUBTASK,
|
|
2055
|
+
description: "Creates a subtask under an existing parent issue. Automatically determines the correct project and subtask issue type. Supports setting priority, assignee, labels, and components.",
|
|
2056
|
+
inputSchema: {
|
|
2057
|
+
type: "object",
|
|
2058
|
+
properties: {
|
|
2059
|
+
parentIssueKey: {
|
|
2060
|
+
type: "string",
|
|
2061
|
+
description: "Parent issue key (e.g., PROJECT-123)"
|
|
2062
|
+
},
|
|
2063
|
+
summary: {
|
|
2064
|
+
type: "string",
|
|
2065
|
+
description: "Subtask summary/title",
|
|
2066
|
+
minLength: 1
|
|
2067
|
+
},
|
|
2068
|
+
description: {
|
|
2069
|
+
type: "string",
|
|
2070
|
+
description: "Detailed subtask description (optional)"
|
|
2071
|
+
},
|
|
2072
|
+
priority: {
|
|
2073
|
+
type: "string",
|
|
2074
|
+
description: "Subtask priority name (e.g., High, Medium, Low) - optional"
|
|
2075
|
+
},
|
|
2076
|
+
assignee: {
|
|
2077
|
+
type: "string",
|
|
2078
|
+
description: "Assignee account ID (optional)"
|
|
2079
|
+
},
|
|
2080
|
+
labels: {
|
|
2081
|
+
type: "array",
|
|
2082
|
+
items: { type: "string" },
|
|
2083
|
+
description: "Subtask labels (optional)",
|
|
2084
|
+
default: []
|
|
2085
|
+
},
|
|
2086
|
+
components: {
|
|
2087
|
+
type: "array",
|
|
2088
|
+
items: { type: "string" },
|
|
2089
|
+
description: "Component names (optional)",
|
|
2090
|
+
default: []
|
|
2091
|
+
}
|
|
2092
|
+
},
|
|
2093
|
+
required: ["parentIssueKey", "summary"]
|
|
2094
|
+
}
|
|
2095
|
+
};
|
|
2096
|
+
async function handleCreateSubtask(input) {
|
|
2097
|
+
try {
|
|
2098
|
+
const validated = validateInput(CreateSubtaskInputSchema, input);
|
|
2099
|
+
log15.info(`Creating subtask under parent issue ${validated.parentIssueKey}...`);
|
|
2100
|
+
const subtaskParams = {
|
|
2101
|
+
summary: validated.summary
|
|
2102
|
+
};
|
|
2103
|
+
if (validated.description !== void 0) subtaskParams.description = validated.description;
|
|
2104
|
+
if (validated.priority !== void 0) subtaskParams.priority = validated.priority;
|
|
2105
|
+
if (validated.assignee !== void 0) subtaskParams.assignee = validated.assignee;
|
|
2106
|
+
if (validated.labels !== void 0) subtaskParams.labels = validated.labels;
|
|
2107
|
+
if (validated.components !== void 0) subtaskParams.components = validated.components;
|
|
2108
|
+
const subtask = await createSubtask(validated.parentIssueKey, subtaskParams);
|
|
2109
|
+
log15.info(`Created subtask ${subtask.key}`);
|
|
2110
|
+
return formatIssueResponse(subtask);
|
|
2111
|
+
} catch (error) {
|
|
2112
|
+
log15.error("Error in handleCreateSubtask:", error);
|
|
2113
|
+
return handleError(error);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// src/index.ts
|
|
2118
|
+
if (process.env.NODE_ENV !== "production") {
|
|
2119
|
+
try {
|
|
2120
|
+
const { config } = await Promise.resolve().then(() => __toESM(require_main(), 1));
|
|
2121
|
+
config();
|
|
2122
|
+
} catch {
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
var log16 = createLogger("server");
|
|
2126
|
+
var toolHandlers = /* @__PURE__ */ new Map([
|
|
2127
|
+
[TOOL_NAMES.GET_VISIBLE_PROJECTS, handleGetVisibleProjects],
|
|
2128
|
+
[TOOL_NAMES.GET_ISSUE, handleGetIssue],
|
|
2129
|
+
[TOOL_NAMES.SEARCH_ISSUES, handleSearchIssues],
|
|
2130
|
+
[TOOL_NAMES.GET_MY_ISSUES, handleGetMyIssues],
|
|
2131
|
+
[TOOL_NAMES.GET_ISSUE_TYPES, handleGetIssueTypes],
|
|
2132
|
+
[TOOL_NAMES.GET_USERS, handleGetUsers],
|
|
2133
|
+
[TOOL_NAMES.GET_PRIORITIES, handleGetPriorities],
|
|
2134
|
+
[TOOL_NAMES.GET_STATUSES, handleGetStatuses],
|
|
2135
|
+
[TOOL_NAMES.CREATE_ISSUE, handleCreateIssue],
|
|
2136
|
+
[TOOL_NAMES.UPDATE_ISSUE, handleUpdateIssue],
|
|
2137
|
+
[TOOL_NAMES.ADD_COMMENT, handleAddComment],
|
|
2138
|
+
[TOOL_NAMES.GET_PROJECT_INFO, handleGetProjectInfo],
|
|
2139
|
+
[TOOL_NAMES.CREATE_SUBTASK, handleCreateSubtask]
|
|
2140
|
+
]);
|
|
2141
|
+
var allTools = [
|
|
2142
|
+
getVisibleProjectsTool,
|
|
2143
|
+
getIssueTool,
|
|
2144
|
+
searchIssuesTool,
|
|
2145
|
+
getMyIssuesTool,
|
|
2146
|
+
getIssueTypesTool,
|
|
2147
|
+
getUsersTool,
|
|
2148
|
+
getPrioritiesTool,
|
|
2149
|
+
getStatusesTool,
|
|
2150
|
+
createIssueTool,
|
|
2151
|
+
updateIssueTool,
|
|
2152
|
+
addCommentTool,
|
|
2153
|
+
getProjectInfoTool,
|
|
2154
|
+
createSubtaskTool
|
|
2155
|
+
];
|
|
2156
|
+
async function main() {
|
|
2157
|
+
try {
|
|
2158
|
+
const auth = validateAuth();
|
|
2159
|
+
console.error(`\u{1F510} Authenticated as: ${auth.email}`);
|
|
2160
|
+
console.error(`\u{1F310} Jira instance: ${auth.baseUrl}`);
|
|
2161
|
+
console.error("\u{1F50D} Testing connection to Jira...");
|
|
2162
|
+
const ok = await testConnection();
|
|
2163
|
+
if (!ok) {
|
|
2164
|
+
console.error("\u274C Connection to Jira failed");
|
|
2165
|
+
process.exit(1);
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
console.error("\u2705 Connection to Jira successful");
|
|
2169
|
+
} catch (error) {
|
|
2170
|
+
console.error("\u274C Authentication Error:", error.message);
|
|
2171
|
+
process.exit(1);
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
let version = "0.0.0";
|
|
2175
|
+
try {
|
|
2176
|
+
const pkg = await Promise.resolve().then(() => __toESM(require_package2(), 1));
|
|
2177
|
+
version = pkg.default?.version || pkg.version || version;
|
|
2178
|
+
} catch {
|
|
2179
|
+
}
|
|
2180
|
+
const server = new Server(
|
|
2181
|
+
{
|
|
2182
|
+
name: "jira-mcp-server",
|
|
2183
|
+
version
|
|
2184
|
+
},
|
|
2185
|
+
{
|
|
2186
|
+
capabilities: {
|
|
2187
|
+
resources: {},
|
|
2188
|
+
tools: {}
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
);
|
|
2192
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2193
|
+
console.error(`\u{1F4CB} Listing ${allTools.length} available tool(s)`);
|
|
2194
|
+
return { tools: allTools };
|
|
2195
|
+
});
|
|
2196
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2197
|
+
const { name, arguments: args } = request.params;
|
|
2198
|
+
console.error(`\u{1F6E0}\uFE0F Executing tool: ${name}`);
|
|
2199
|
+
const handler = toolHandlers.get(name);
|
|
2200
|
+
if (!handler) {
|
|
2201
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
2202
|
+
}
|
|
2203
|
+
try {
|
|
2204
|
+
const result = await handler(args);
|
|
2205
|
+
console.error(`\u2705 Tool ${name} executed successfully`);
|
|
2206
|
+
return result;
|
|
2207
|
+
} catch (error) {
|
|
2208
|
+
console.error(`\u274C Error executing tool ${name}:`, error.message);
|
|
2209
|
+
throw error;
|
|
2210
|
+
}
|
|
2211
|
+
});
|
|
2212
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
2213
|
+
return { resources: [] };
|
|
2214
|
+
});
|
|
2215
|
+
server.setRequestHandler(ReadResourceRequestSchema, async () => {
|
|
2216
|
+
throw new Error("Resource reading not implemented");
|
|
2217
|
+
});
|
|
2218
|
+
const transport = new StdioServerTransport();
|
|
2219
|
+
await server.connect(transport);
|
|
2220
|
+
console.error("\u{1F680} Jira MCP server running on stdio");
|
|
2221
|
+
}
|
|
2222
|
+
main().catch((error) => {
|
|
2223
|
+
log16.error("Fatal error:", error);
|
|
2224
|
+
process.exit(1);
|
|
2225
|
+
});
|
|
2226
|
+
//# sourceMappingURL=index.js.map
|
|
2227
|
+
//# sourceMappingURL=index.js.map
|