mcp-jira-stdio 1.2.0 → 1.4.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/README.md +14 -2
- package/dist/index.js +66 -70
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/mcp-jira-stdio)
|
|
7
7
|
[](https://glama.ai/mcp/servers)
|
|
8
8
|
|
|
9
|
-
[](https://hub.docker.com/r/freema/mcp-jira-stdio)
|
|
10
9
|
[](https://github.com/freema/mcp-jira-stdio/actions/workflows/ci.yml)
|
|
11
10
|
[](https://codecov.io/gh/freema/mcp-jira-stdio)
|
|
12
11
|
[](https://github.com/freema/mcp-jira-stdio/issues)
|
|
13
|
-
[](https://hub.docker.com/r/freema/mcp-jira-stdio)
|
|
14
12
|
|
|
15
13
|
<a href="https://glama.ai/mcp/servers/@freema/mcp-jira-stdio">
|
|
16
14
|
<img width="380" height="200" src="https://glama.ai/mcp/servers/@freema/mcp-jira-stdio/badge" />
|
|
@@ -212,6 +210,11 @@ npm run inspector:dev
|
|
|
212
210
|
task inspector:dev
|
|
213
211
|
```
|
|
214
212
|
|
|
213
|
+
Notes:
|
|
214
|
+
|
|
215
|
+
- Startup no longer blocks on Jira connectivity. If Jira env vars are missing, the server still starts and lists tools; tool calls will fail with a clear auth error until you set `JIRA_BASE_URL`, `JIRA_EMAIL`, and `JIRA_API_TOKEN`.
|
|
216
|
+
- Connection testing runs only in development/test (`NODE_ENV=development` or `test`). Failures are logged but do not terminate the server, so the inspector can still display tools.
|
|
217
|
+
|
|
215
218
|
### Testing
|
|
216
219
|
|
|
217
220
|
```bash
|
|
@@ -314,6 +317,15 @@ task inspector:dev
|
|
|
314
317
|
task check
|
|
315
318
|
```
|
|
316
319
|
|
|
320
|
+
If the inspector shows an SSE error and the server exits immediately, ensure you are not forcing an early exit with invalid credentials. With the current behavior, the server should not exit on missing credentials; export your Jira vars to exercise the tools:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
export JIRA_BASE_URL="https://your-instance.atlassian.net"
|
|
324
|
+
export JIRA_EMAIL="your-email@example.com"
|
|
325
|
+
export JIRA_API_TOKEN="your-api-token"
|
|
326
|
+
npm run inspector
|
|
327
|
+
```
|
|
328
|
+
|
|
317
329
|
## 🔍 Environment Variables
|
|
318
330
|
|
|
319
331
|
| Variable | Required | Description | Example |
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ var require_package = __commonJS({
|
|
|
42
42
|
"node_modules/dotenv/package.json"(exports, module) {
|
|
43
43
|
module.exports = {
|
|
44
44
|
name: "dotenv",
|
|
45
|
-
version: "17.2.
|
|
45
|
+
version: "17.2.3",
|
|
46
46
|
description: "Loads environment variables from .env file",
|
|
47
47
|
main: "lib/main.js",
|
|
48
48
|
types: "lib/main.d.ts",
|
|
@@ -64,8 +64,8 @@ var require_package = __commonJS({
|
|
|
64
64
|
"dts-check": "tsc --project tests/types/tsconfig.json",
|
|
65
65
|
lint: "standard",
|
|
66
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",
|
|
67
|
+
test: "tap run tests/**/*.js --allow-empty-coverage --disable-coverage --timeout=60000",
|
|
68
|
+
"test:coverage": "tap run tests/**/*.js --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov",
|
|
69
69
|
prerelease: "npm test",
|
|
70
70
|
release: "standard-version"
|
|
71
71
|
},
|
|
@@ -118,9 +118,12 @@ var require_main = __commonJS({
|
|
|
118
118
|
"\u{1F510} encrypt with Dotenvx: https://dotenvx.com",
|
|
119
119
|
"\u{1F510} prevent committing .env to code: https://dotenvx.com/precommit",
|
|
120
120
|
"\u{1F510} prevent building .env in docker: https://dotenvx.com/prebuild",
|
|
121
|
-
"\u{1F4E1}
|
|
122
|
-
"\u{
|
|
123
|
-
"\u{
|
|
121
|
+
"\u{1F4E1} add observability to secrets: https://dotenvx.com/ops",
|
|
122
|
+
"\u{1F465} sync secrets across teammates & machines: https://dotenvx.com/ops",
|
|
123
|
+
"\u{1F5C2}\uFE0F backup and recover secrets: https://dotenvx.com/ops",
|
|
124
|
+
"\u2705 audit secrets and track compliance: https://dotenvx.com/ops",
|
|
125
|
+
"\u{1F504} add secrets lifecycle management: https://dotenvx.com/ops",
|
|
126
|
+
"\u{1F511} add access controls to secrets: https://dotenvx.com/ops",
|
|
124
127
|
"\u{1F6E0}\uFE0F run anywhere with `dotenvx run -- yourcommand`",
|
|
125
128
|
"\u2699\uFE0F specify custom .env file path with { path: '/custom/path/.env' }",
|
|
126
129
|
"\u2699\uFE0F enable debug logging with { debug: true }",
|
|
@@ -436,7 +439,7 @@ var require_package2 = __commonJS({
|
|
|
436
439
|
"package.json"(exports, module) {
|
|
437
440
|
module.exports = {
|
|
438
441
|
name: "mcp-jira-stdio",
|
|
439
|
-
version: "1.
|
|
442
|
+
version: "1.4.0",
|
|
440
443
|
description: "Model Context Protocol (MCP) server for Jira API integration",
|
|
441
444
|
author: "graslt",
|
|
442
445
|
license: "MIT",
|
|
@@ -694,18 +697,6 @@ async function makeJiraRequest(config, retryConfig = {
|
|
|
694
697
|
}
|
|
695
698
|
throw lastError;
|
|
696
699
|
}
|
|
697
|
-
async function testConnection() {
|
|
698
|
-
try {
|
|
699
|
-
await makeJiraRequest({
|
|
700
|
-
method: "GET",
|
|
701
|
-
url: "/myself"
|
|
702
|
-
});
|
|
703
|
-
return true;
|
|
704
|
-
} catch (error) {
|
|
705
|
-
log.error("Connection test failed:", error);
|
|
706
|
-
return false;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
700
|
function validateInput(schema, input) {
|
|
710
701
|
try {
|
|
711
702
|
return schema.parse(input);
|
|
@@ -727,13 +718,10 @@ function isValidProjectKey(key) {
|
|
|
727
718
|
function sanitizeJQL(jql) {
|
|
728
719
|
return jql.replace(/[<>"'`]/g, "").replace(/\s+/g, " ").trim();
|
|
729
720
|
}
|
|
730
|
-
function
|
|
731
|
-
if (
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if (maxResults < 1 || maxResults > 100) {
|
|
735
|
-
throw new Error("maxResults must be between 1 and 100");
|
|
736
|
-
}
|
|
721
|
+
function extractIssueKey(input) {
|
|
722
|
+
if (typeof input !== "string") return null;
|
|
723
|
+
const match = input.match(/[A-Z][A-Z0-9]*-\d+/);
|
|
724
|
+
return match ? match[0] : null;
|
|
737
725
|
}
|
|
738
726
|
|
|
739
727
|
// src/types/tools.ts
|
|
@@ -750,8 +738,10 @@ var GetIssueInputSchema = z.object({
|
|
|
750
738
|
});
|
|
751
739
|
var SearchIssuesInputSchema = z.object({
|
|
752
740
|
jql: z.string().min(1).describe("JQL query string"),
|
|
753
|
-
|
|
754
|
-
|
|
741
|
+
nextPageToken: z.string().optional().describe(
|
|
742
|
+
"Token for pagination. Omit for first page, use value from previous response for next page."
|
|
743
|
+
),
|
|
744
|
+
maxResults: z.number().min(1).max(100).default(50).describe("Maximum number of results to return per page"),
|
|
755
745
|
fields: z.array(z.string()).optional().describe("Specific fields to retrieve"),
|
|
756
746
|
expand: z.array(z.string()).optional().describe("Additional details to include")
|
|
757
747
|
});
|
|
@@ -780,8 +770,10 @@ var UpdateIssueInputSchema = z.object({
|
|
|
780
770
|
returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after update")
|
|
781
771
|
});
|
|
782
772
|
var GetMyIssuesInputSchema = z.object({
|
|
783
|
-
|
|
784
|
-
|
|
773
|
+
nextPageToken: z.string().optional().describe(
|
|
774
|
+
"Token for pagination. Omit for first page, use value from previous response for next page."
|
|
775
|
+
),
|
|
776
|
+
maxResults: z.number().min(1).max(100).default(50).describe("Maximum number of results to return per page"),
|
|
785
777
|
fields: z.array(z.string()).optional().describe("Specific fields to retrieve"),
|
|
786
778
|
expand: z.array(z.string()).optional().describe("Additional details to include")
|
|
787
779
|
});
|
|
@@ -968,14 +960,15 @@ async function getIssue(issueKey, options = {}) {
|
|
|
968
960
|
return await makeJiraRequest(config);
|
|
969
961
|
}
|
|
970
962
|
async function searchIssues(options) {
|
|
971
|
-
const { jql,
|
|
972
|
-
validatePagination(startAt, maxResults);
|
|
963
|
+
const { jql, nextPageToken, maxResults = 50 } = options;
|
|
973
964
|
const sanitizedJql = sanitizeJQL(jql);
|
|
974
965
|
const data = {
|
|
975
966
|
jql: sanitizedJql,
|
|
976
|
-
startAt,
|
|
977
967
|
maxResults
|
|
978
968
|
};
|
|
969
|
+
if (nextPageToken) {
|
|
970
|
+
data.nextPageToken = nextPageToken;
|
|
971
|
+
}
|
|
979
972
|
if (options.fields) {
|
|
980
973
|
data.fields = options.fields;
|
|
981
974
|
}
|
|
@@ -984,7 +977,7 @@ async function searchIssues(options) {
|
|
|
984
977
|
}
|
|
985
978
|
const config = {
|
|
986
979
|
method: "POST",
|
|
987
|
-
url: "/search",
|
|
980
|
+
url: "/search/jql",
|
|
988
981
|
data
|
|
989
982
|
};
|
|
990
983
|
return await makeJiraRequest(config);
|
|
@@ -1059,9 +1052,9 @@ async function getMyIssues(options = {}) {
|
|
|
1059
1052
|
const jql = `assignee = currentUser() ORDER BY updated DESC`;
|
|
1060
1053
|
const searchParams = {
|
|
1061
1054
|
jql,
|
|
1062
|
-
startAt: options.startAt || 0,
|
|
1063
1055
|
maxResults: options.maxResults || 50
|
|
1064
1056
|
};
|
|
1057
|
+
if (options.nextPageToken !== void 0) searchParams.nextPageToken = options.nextPageToken;
|
|
1065
1058
|
if (options.fields !== void 0) searchParams.fields = options.fields;
|
|
1066
1059
|
if (options.expand !== void 0) searchParams.expand = options.expand;
|
|
1067
1060
|
return await searchIssues(searchParams);
|
|
@@ -1321,9 +1314,18 @@ function formatSearchResultsResponse(result) {
|
|
|
1321
1314
|
const rangeStart = result.startAt + 1;
|
|
1322
1315
|
const rangeEnd = Math.min(result.startAt + result.maxResults, result.total);
|
|
1323
1316
|
const showRangeInline = result.total > result.maxResults || result.startAt > 0;
|
|
1324
|
-
|
|
1317
|
+
let paginationInfo = "";
|
|
1318
|
+
if (showRangeInline) {
|
|
1319
|
+
paginationInfo = `
|
|
1325
1320
|
|
|
1326
|
-
*Showing ${rangeStart}-${rangeEnd} of ${result.total} results
|
|
1321
|
+
*Showing ${rangeStart}-${rangeEnd} of ${result.total} results*`;
|
|
1322
|
+
}
|
|
1323
|
+
if (result.nextPageToken && !result.isLast) {
|
|
1324
|
+
paginationInfo += `
|
|
1325
|
+
*Next page token: \`${result.nextPageToken}\`*`;
|
|
1326
|
+
} else if (result.isLast) {
|
|
1327
|
+
paginationInfo += "\n*This is the last page of results*";
|
|
1328
|
+
}
|
|
1327
1329
|
return {
|
|
1328
1330
|
content: [
|
|
1329
1331
|
{
|
|
@@ -1639,7 +1641,8 @@ var getIssueTool = {
|
|
|
1639
1641
|
async function handleGetIssue(input) {
|
|
1640
1642
|
try {
|
|
1641
1643
|
const validated = validateInput(GetIssueInputSchema, input);
|
|
1642
|
-
const
|
|
1644
|
+
const trimmedInput = (validated.issueKey || "").trim();
|
|
1645
|
+
const key = extractIssueKey(trimmedInput) || trimmedInput;
|
|
1643
1646
|
log4.info(`Getting issue ${key}...`);
|
|
1644
1647
|
const getParams = {};
|
|
1645
1648
|
if (validated.expand !== void 0) getParams.expand = validated.expand;
|
|
@@ -1657,7 +1660,7 @@ async function handleGetIssue(input) {
|
|
|
1657
1660
|
var log5 = createLogger("tool:search-issues");
|
|
1658
1661
|
var searchIssuesTool = {
|
|
1659
1662
|
name: TOOL_NAMES.SEARCH_ISSUES,
|
|
1660
|
-
description: 'Search for Jira issues using JQL. Supports complex queries with pagination and field selection. Examples: "project = PROJECT AND status = Open", "assignee = currentUser()".',
|
|
1663
|
+
description: 'Search for Jira issues using JQL. Supports complex queries with pagination and field selection. Examples: "project = PROJECT AND status = Open", "assignee = currentUser()". For pagination, use nextPageToken from previous response.',
|
|
1661
1664
|
inputSchema: {
|
|
1662
1665
|
type: "object",
|
|
1663
1666
|
properties: {
|
|
@@ -1665,15 +1668,13 @@ var searchIssuesTool = {
|
|
|
1665
1668
|
type: "string",
|
|
1666
1669
|
description: 'JQL query string (e.g., "project = PROJECT AND status = Open")'
|
|
1667
1670
|
},
|
|
1668
|
-
|
|
1669
|
-
type: "
|
|
1670
|
-
description: "
|
|
1671
|
-
minimum: 0,
|
|
1672
|
-
default: 0
|
|
1671
|
+
nextPageToken: {
|
|
1672
|
+
type: "string",
|
|
1673
|
+
description: "Token for pagination. Omit for first page, use value from previous response for next page."
|
|
1673
1674
|
},
|
|
1674
1675
|
maxResults: {
|
|
1675
1676
|
type: "number",
|
|
1676
|
-
description: "Maximum number of results to return",
|
|
1677
|
+
description: "Maximum number of results to return per page",
|
|
1677
1678
|
minimum: 1,
|
|
1678
1679
|
maximum: 100,
|
|
1679
1680
|
default: 50
|
|
@@ -1700,7 +1701,7 @@ async function handleSearchIssues(input) {
|
|
|
1700
1701
|
const searchParams = {
|
|
1701
1702
|
jql: validated.jql
|
|
1702
1703
|
};
|
|
1703
|
-
if (validated.
|
|
1704
|
+
if (validated.nextPageToken !== void 0) searchParams.nextPageToken = validated.nextPageToken;
|
|
1704
1705
|
if (validated.maxResults !== void 0) searchParams.maxResults = validated.maxResults;
|
|
1705
1706
|
if (validated.fields !== void 0) searchParams.fields = validated.fields;
|
|
1706
1707
|
if (validated.expand !== void 0) searchParams.expand = validated.expand;
|
|
@@ -1717,19 +1718,17 @@ async function handleSearchIssues(input) {
|
|
|
1717
1718
|
var log6 = createLogger("tool:get-my-issues");
|
|
1718
1719
|
var getMyIssuesTool = {
|
|
1719
1720
|
name: TOOL_NAMES.GET_MY_ISSUES,
|
|
1720
|
-
description: "Retrieves issues assigned to current user, sorted by most recently updated first. Supports pagination and field selection.",
|
|
1721
|
+
description: "Retrieves issues assigned to current user, sorted by most recently updated first. Supports pagination and field selection. For pagination, use nextPageToken from previous response.",
|
|
1721
1722
|
inputSchema: {
|
|
1722
1723
|
type: "object",
|
|
1723
1724
|
properties: {
|
|
1724
|
-
|
|
1725
|
-
type: "
|
|
1726
|
-
description: "
|
|
1727
|
-
minimum: 0,
|
|
1728
|
-
default: 0
|
|
1725
|
+
nextPageToken: {
|
|
1726
|
+
type: "string",
|
|
1727
|
+
description: "Token for pagination. Omit for first page, use value from previous response for next page."
|
|
1729
1728
|
},
|
|
1730
1729
|
maxResults: {
|
|
1731
1730
|
type: "number",
|
|
1732
|
-
description: "Maximum number of results to return",
|
|
1731
|
+
description: "Maximum number of results to return per page",
|
|
1733
1732
|
minimum: 1,
|
|
1734
1733
|
maximum: 100,
|
|
1735
1734
|
default: 50
|
|
@@ -1754,7 +1753,7 @@ async function handleGetMyIssues(input) {
|
|
|
1754
1753
|
const validated = validateInput(GetMyIssuesInputSchema, input);
|
|
1755
1754
|
log6.info("Getting issues assigned to current user...");
|
|
1756
1755
|
const getParams = {};
|
|
1757
|
-
if (validated.
|
|
1756
|
+
if (validated.nextPageToken !== void 0) getParams.nextPageToken = validated.nextPageToken;
|
|
1758
1757
|
if (validated.maxResults !== void 0) getParams.maxResults = validated.maxResults;
|
|
1759
1758
|
if (validated.fields !== void 0) getParams.fields = validated.fields;
|
|
1760
1759
|
if (validated.expand !== void 0) getParams.expand = validated.expand;
|
|
@@ -2291,25 +2290,22 @@ var allTools = [
|
|
|
2291
2290
|
async function main() {
|
|
2292
2291
|
const isDryRun = (process.env.DRY_RUN || "").toLowerCase() === "1" || (process.env.DRY_RUN || "").toLowerCase() === "true";
|
|
2293
2292
|
if (!isDryRun) {
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
console.error(
|
|
2302
|
-
|
|
2303
|
-
|
|
2293
|
+
const hasAuthVars = Boolean(
|
|
2294
|
+
process.env.JIRA_BASE_URL && process.env.JIRA_EMAIL && process.env.JIRA_API_TOKEN
|
|
2295
|
+
);
|
|
2296
|
+
if (hasAuthVars) {
|
|
2297
|
+
try {
|
|
2298
|
+
const auth = validateAuth();
|
|
2299
|
+
console.error(`\u{1F510} Authenticated as: ${auth.email}`);
|
|
2300
|
+
console.error(`\u{1F310} Jira instance: ${auth.baseUrl}`);
|
|
2301
|
+
} catch (error) {
|
|
2302
|
+
console.error("\u26A0\uFE0F Invalid Jira credentials:", error.message);
|
|
2304
2303
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
console.error("\u274C Authentication Error:", error.message);
|
|
2308
|
-
process.exit(1);
|
|
2309
|
-
return;
|
|
2304
|
+
} else {
|
|
2305
|
+
console.error("\u26A0\uFE0F Jira env vars missing (JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN)");
|
|
2310
2306
|
}
|
|
2311
2307
|
} else {
|
|
2312
|
-
console.error("\u{1F9EA} DRY_RUN=1 set \u2014 skipping Jira auth
|
|
2308
|
+
console.error("\u{1F9EA} DRY_RUN=1 set \u2014 skipping Jira auth check");
|
|
2313
2309
|
}
|
|
2314
2310
|
let version = "0.0.0";
|
|
2315
2311
|
try {
|