@thammarongg/jira-mcp 0.3.0 → 0.5.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 +56 -11
- package/dist/adf.js +71 -0
- package/dist/agents.js +412 -0
- package/dist/client.js +6 -1
- package/dist/config.js +1 -1
- package/dist/index.js +54 -3
- package/dist/install.js +47 -381
- package/dist/prompt.js +139 -0
- package/dist/status.js +81 -0
- package/dist/tools/boards.js +3 -3
- package/dist/tools/epics.js +30 -12
- package/dist/tools/issues.js +130 -32
- package/dist/tools/projects.js +4 -2
- package/dist/tools/sprints.js +41 -23
- package/dist/tools/users.js +1 -1
- package/dist/uninstall.js +140 -0
- package/dist/update.js +71 -0
- package/package.json +1 -1
- package/skill/SKILL.md +53 -5
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { AGENTS, hasAgentEntry, removeAgentConfig, resolveAgents } from "./agents.js";
|
|
2
|
+
import { makePrompter } from "./prompt.js";
|
|
3
|
+
function parseFlags(argv) {
|
|
4
|
+
const flags = {};
|
|
5
|
+
for (let i = 0; i < argv.length; i++) {
|
|
6
|
+
const arg = argv[i];
|
|
7
|
+
if (arg === "--agents") {
|
|
8
|
+
const value = argv[++i];
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
throw new Error("Missing value for --agents");
|
|
11
|
+
flags.agents = value;
|
|
12
|
+
}
|
|
13
|
+
else if (arg === "--yes") {
|
|
14
|
+
flags.yes = true;
|
|
15
|
+
}
|
|
16
|
+
else if (arg === "--help" || arg === "-h") {
|
|
17
|
+
flags.help = true;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
throw new Error(`Unknown option: ${arg} (see --help)`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return flags;
|
|
24
|
+
}
|
|
25
|
+
function printUsage() {
|
|
26
|
+
console.log(`jira-mcp uninstall — remove jira MCP entries
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
npx -y @thammarongg/jira-mcp uninstall
|
|
30
|
+
npx -y @thammarongg/jira-mcp uninstall --agents all --yes
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
--agents <list> Comma-separated agent ids or 'all'
|
|
34
|
+
--yes Skip the confirmation prompt
|
|
35
|
+
-h, --help Show this help
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
function configuredAgents() {
|
|
39
|
+
return AGENTS.filter((agent) => hasAgentEntry(agent));
|
|
40
|
+
}
|
|
41
|
+
function resolveConfigured(spec, configured) {
|
|
42
|
+
const norm = spec.trim().toLowerCase();
|
|
43
|
+
if (norm === "all" || norm === "a")
|
|
44
|
+
return [...configured];
|
|
45
|
+
const ids = norm.split(/[,\s]+/).filter(Boolean);
|
|
46
|
+
const selected = [];
|
|
47
|
+
for (const id of ids) {
|
|
48
|
+
const agent = /^\d+$/.test(id)
|
|
49
|
+
? configured[Number(id) - 1]
|
|
50
|
+
: configured.find((candidate) => candidate.id === id);
|
|
51
|
+
if (!agent) {
|
|
52
|
+
throw new Error(`Unknown configured agent '${id}'. Valid: ${configured.map((candidate, index) => `${index + 1}/${candidate.id}`).join(", ")} or all`);
|
|
53
|
+
}
|
|
54
|
+
if (!selected.includes(agent))
|
|
55
|
+
selected.push(agent);
|
|
56
|
+
}
|
|
57
|
+
if (selected.length === 0)
|
|
58
|
+
throw new Error("No agents selected");
|
|
59
|
+
return selected;
|
|
60
|
+
}
|
|
61
|
+
async function promptSelection(p, configured) {
|
|
62
|
+
console.log("\njira-mcp uninstall — select agents to remove:\n");
|
|
63
|
+
console.log(" a) Select All");
|
|
64
|
+
configured.forEach((agent, index) => {
|
|
65
|
+
console.log(` ${index + 1}) ${agent.label.padEnd(15)} ${agent.path}`);
|
|
66
|
+
});
|
|
67
|
+
for (;;) {
|
|
68
|
+
const answer = (await p.ask("\nChoice (e.g. 1,3 or a): ")).trim().toLowerCase();
|
|
69
|
+
if (answer === "" && p.closed)
|
|
70
|
+
throw new Error("No input received (stdin closed)");
|
|
71
|
+
try {
|
|
72
|
+
return resolveConfigured(answer, configured);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (p.closed)
|
|
76
|
+
throw err;
|
|
77
|
+
console.log(` ${err instanceof Error ? err.message : String(err)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function runUninstall(argv) {
|
|
82
|
+
const flags = parseFlags(argv);
|
|
83
|
+
if (flags.help) {
|
|
84
|
+
printUsage();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const configured = configuredAgents();
|
|
88
|
+
if (configured.length === 0) {
|
|
89
|
+
console.error("No jira MCP entries found. Run install to configure an agent first.");
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const p = !flags.yes ? makePrompter() : undefined;
|
|
94
|
+
try {
|
|
95
|
+
const selected = flags.agents
|
|
96
|
+
? resolveAgents(flags.agents)
|
|
97
|
+
: flags.yes
|
|
98
|
+
? [...configured]
|
|
99
|
+
: await promptSelection(p, configured);
|
|
100
|
+
console.log("\nWill remove the jira MCP server from:");
|
|
101
|
+
for (const agent of selected) {
|
|
102
|
+
if (hasAgentEntry(agent))
|
|
103
|
+
console.log(` - ${agent.label.padEnd(15)} ${agent.path}`);
|
|
104
|
+
else
|
|
105
|
+
console.log(` skip ${agent.label.padEnd(15)} ${agent.path} (no jira entry)`);
|
|
106
|
+
}
|
|
107
|
+
if (!flags.yes) {
|
|
108
|
+
const answer = (await p.ask("\nProceed? [y/N] ")).trim().toLowerCase();
|
|
109
|
+
if (answer !== "y" && answer !== "yes") {
|
|
110
|
+
console.log("Aborted.");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let removed = 0;
|
|
115
|
+
let failed = false;
|
|
116
|
+
for (const agent of selected) {
|
|
117
|
+
try {
|
|
118
|
+
const result = removeAgentConfig(agent);
|
|
119
|
+
if (result === "removed") {
|
|
120
|
+
removed++;
|
|
121
|
+
console.log(` ok ${agent.label.padEnd(15)} ${agent.path} (removed)`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.log(` skip ${agent.label.padEnd(15)} ${agent.path} (no jira entry)`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
failed = true;
|
|
129
|
+
console.error(` ERR ${agent.label.padEnd(15)} ${agent.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (removed === 0 || failed)
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
if (removed > 0)
|
|
135
|
+
console.log("\nDone. Restart each agent to pick up the change.");
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
p?.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { AGENTS, hasAgentEntry, readAgentEnv, resolveAgents, writeAgentConfig } from "./agents.js";
|
|
2
|
+
function parseFlags(argv) {
|
|
3
|
+
const flags = {};
|
|
4
|
+
for (let i = 0; i < argv.length; i++) {
|
|
5
|
+
const arg = argv[i];
|
|
6
|
+
if (arg === "--agents") {
|
|
7
|
+
const value = argv[++i];
|
|
8
|
+
if (value === undefined)
|
|
9
|
+
throw new Error("Missing value for --agents");
|
|
10
|
+
flags.agents = value;
|
|
11
|
+
}
|
|
12
|
+
else if (arg === "--yes") {
|
|
13
|
+
// Accepted for scripts that use the same non-interactive convention as install/uninstall.
|
|
14
|
+
}
|
|
15
|
+
else if (arg === "--help" || arg === "-h") {
|
|
16
|
+
flags.help = true;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
throw new Error(`Unknown option: ${arg} (see --help)`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return flags;
|
|
23
|
+
}
|
|
24
|
+
function printUsage() {
|
|
25
|
+
console.log(`jira-mcp update — refresh existing jira MCP entries
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
npx -y @thammarongg/jira-mcp update [--agents <list>]
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--agents <list> Comma-separated agent ids or 'all'
|
|
32
|
+
-h, --help Show this help
|
|
33
|
+
`);
|
|
34
|
+
}
|
|
35
|
+
export async function runUpdate(argv) {
|
|
36
|
+
const flags = parseFlags(argv);
|
|
37
|
+
if (flags.help) {
|
|
38
|
+
printUsage();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const selected = flags.agents ? resolveAgents(flags.agents) : [...AGENTS];
|
|
42
|
+
let found = 0;
|
|
43
|
+
let failed = false;
|
|
44
|
+
for (const agent of selected) {
|
|
45
|
+
const env = readAgentEnv(agent);
|
|
46
|
+
if (!env) {
|
|
47
|
+
const reason = hasAgentEntry(agent) ? "no readable jira credentials" : "no jira entry";
|
|
48
|
+
console.log(` skip ${agent.label.padEnd(15)} ${agent.path} (${reason})`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
found++;
|
|
52
|
+
try {
|
|
53
|
+
const result = writeAgentConfig(agent, env);
|
|
54
|
+
if (result === "skipped")
|
|
55
|
+
console.log(` skip ${agent.label.padEnd(15)} ${agent.path}`);
|
|
56
|
+
else
|
|
57
|
+
console.log(` ok ${agent.label.padEnd(15)} ${agent.path} (${result})`);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
failed = true;
|
|
61
|
+
console.error(` ERR ${agent.label.padEnd(15)} ${agent.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (found === 0) {
|
|
65
|
+
console.error("No jira MCP entries with credentials were found. Run install to configure an agent first.");
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
}
|
|
68
|
+
else if (failed) {
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -27,11 +27,35 @@ Or non-interactive: `npx -y @thammarongg/jira-mcp install --agents all
|
|
|
27
27
|
--yes`. Required env vars:
|
|
28
28
|
|
|
29
29
|
- `JIRA_BASE_URL` — `https://your-org.atlassian.net` (Cloud) or
|
|
30
|
-
`https://jira.yourcompany.com` (DC)
|
|
30
|
+
`https://jira.yourcompany.com` (DC); a DC context path such as `/jira` is
|
|
31
|
+
supported and preserved
|
|
31
32
|
- Cloud: `JIRA_EMAIL` + `JIRA_API_TOKEN` (token from id.atlassian.com →
|
|
32
33
|
Security → API tokens)
|
|
33
34
|
- DC: `JIRA_USERNAME` + `JIRA_API_TOKEN` (PAT) or `JIRA_PASSWORD` (app password)
|
|
34
35
|
|
|
36
|
+
When a selected agent already has a `jira` entry, the interactive installer
|
|
37
|
+
offers to reuse its base URL and account; `--yes` with no credential flags
|
|
38
|
+
reuses those credentials without prompting.
|
|
39
|
+
|
|
40
|
+
The CLI also supports these maintenance commands:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx -y @thammarongg/jira-mcp update # refresh configured entries
|
|
44
|
+
npx -y @thammarongg/jira-mcp update --agents codex
|
|
45
|
+
npx -y @thammarongg/jira-mcp uninstall # choose entries to remove
|
|
46
|
+
npx -y @thammarongg/jira-mcp uninstall --agents all --yes
|
|
47
|
+
npx -y @thammarongg/jira-mcp --version
|
|
48
|
+
npx -y @thammarongg/jira-mcp --help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`update` is non-interactive and skips agents without a `jira` entry.
|
|
52
|
+
`uninstall` preserves every other config key and confirms before removing the
|
|
53
|
+
selected entries unless `--yes` is supplied. Running the command with no
|
|
54
|
+
arguments in a terminal shows a status card; MCP clients use piped stdio, so
|
|
55
|
+
the same command starts the server and expects `JIRA_*` environment variables.
|
|
56
|
+
Without those variables, a bare server start reports a missing
|
|
57
|
+
`JIRA_BASE_URL`.
|
|
58
|
+
|
|
35
59
|
Claude Code one-liner:
|
|
36
60
|
|
|
37
61
|
```bash
|
|
@@ -64,12 +88,17 @@ After setup, verify with the `get_current_user` tool.
|
|
|
64
88
|
|
|
65
89
|
## Tool map
|
|
66
90
|
|
|
67
|
-
- **Boards/sprints**: `list_boards
|
|
91
|
+
- **Boards/sprints**: `list_boards` (the project filter is one Agile
|
|
92
|
+
`projectKeyOrId` value), `get_board`, `list_sprints`, `get_sprint`,
|
|
68
93
|
`create_sprint`, `update_sprint`, `close_sprint`, `get_sprint_issues`,
|
|
69
|
-
`get_sprint_view` (UI-like full view), `get_backlog
|
|
94
|
+
`get_sprint_view` (UI-like full view), `get_backlog`. Cloud backlog and
|
|
95
|
+
issue-list reads use Software 1.0 enhanced endpoints with cursor paging;
|
|
96
|
+
Data Center uses Agile 1.0 with `startAt`/`total`. `create_sprint` sends
|
|
97
|
+
`originBoardId` to the top-level Agile sprint-create route.
|
|
70
98
|
- **Epics**: `list_epics` (by board), `get_epic`, `get_epic_issues`,
|
|
71
99
|
`create_epic`, `move_issue_to_epic`, `get_epic_meta` — epics are addressed by
|
|
72
|
-
key or numeric ID
|
|
100
|
+
key or numeric ID. Cloud `get_epic_issues` uses Software 1.0 enhanced
|
|
101
|
+
cursor paging; team-managed projects fall back to JQL/search as needed.
|
|
73
102
|
- **Issues**: `get_issue`, `create_issue`, `update_issue`, `delete_issue`,
|
|
74
103
|
`search_issues` (JQL), `get_issue_create_meta`, `get_issue_transitions`,
|
|
75
104
|
`transition_issue`, `assign_issue`, `add_comment`, `list_comments`,
|
|
@@ -86,7 +115,8 @@ After setup, verify with the `get_current_user` tool.
|
|
|
86
115
|
`active`) → `get_sprint_issues` or `get_sprint_view` for the full picture.
|
|
87
116
|
|
|
88
117
|
**Backlog review**: `get_backlog(boardId)` — issues on the board that are not
|
|
89
|
-
in a sprint, ordered by rank; pass `jql` to narrow it.
|
|
118
|
+
in a sprint, ordered by rank; pass `jql` to narrow it. On Cloud, pass the
|
|
119
|
+
returned `nextPageToken` for the next page; on Data Center, pass `startAt`.
|
|
90
120
|
|
|
91
121
|
**Epic breakdown**: `list_epics(boardId)` → `get_epic_issues(epicIdOrKey)` for
|
|
92
122
|
the children. To create one, `get_epic_meta(projectKey)` gives the epic issue
|
|
@@ -96,6 +126,16 @@ custom field via `customFields`.
|
|
|
96
126
|
**Create an issue**: call `get_issue_create_meta(projectKeys=...)` first to
|
|
97
127
|
discover valid issue types and required fields, then `create_issue`
|
|
98
128
|
(custom fields go in `customFields`, e.g. `{ "customfield_10010": "..." }`).
|
|
129
|
+
The metadata tool composes the current per-project and per-issue-type
|
|
130
|
+
endpoints; the deprecated `/issue/createmeta` query form is not used. Pass
|
|
131
|
+
`projectKeys` when fields are expanded (the default); omit it only with
|
|
132
|
+
`expand: "projects.issuetypes"` for issue-type summaries. Metadata requests
|
|
133
|
+
use a fixed concurrency limit of four.
|
|
134
|
+
|
|
135
|
+
**Comments and descriptions**: pass plain text — `add_comment`, `create_issue`,
|
|
136
|
+
`create_epic`, `update_issue` and `add_worklog` convert it to ADF automatically
|
|
137
|
+
on Jira Cloud (v3). Blank lines separate paragraphs; Markdown is not rendered.
|
|
138
|
+
No need to hand-build an ADF document or fall back to `jira_api`.
|
|
99
139
|
|
|
100
140
|
**Move work through the workflow**: `get_issue_transitions(issueKey)` to see
|
|
101
141
|
available transitions and required fields, then `transition_issue`.
|
|
@@ -120,6 +160,14 @@ available transitions and required fields, then `transition_issue`.
|
|
|
120
160
|
On Data Center it still uses `startAt`/`total`.
|
|
121
161
|
- Assignees: pass an account ID on Cloud, a username on DC — the server maps
|
|
122
162
|
it to the right field shape automatically.
|
|
163
|
+
- `get_user` uses `/user?accountId=...` on Cloud/v3 and `/user?username=...` on
|
|
164
|
+
Data Center/v2. `create_issue` maps its `dueDate` input to Jira's `duedate`
|
|
165
|
+
field, and `add_worklog` accepts an optional `started` timestamp (Jira
|
|
166
|
+
defaults it to now when omitted).
|
|
167
|
+
- Data Center exposes REST API v2/latest, not `/rest/api/3`; do not force
|
|
168
|
+
`JIRA_API_VERSION=3` for a DC host. The default deployment detection routes
|
|
169
|
+
Cloud (`*.atlassian.net`) and DC endpoints separately, while ADF and user
|
|
170
|
+
field shapes follow the selected API version.
|
|
123
171
|
- To remove an issue from an epic: `update_issue` with
|
|
124
172
|
`fields: { "epic": null }`.
|
|
125
173
|
- Anything not covered by a dedicated tool → `jira_api` with method, path
|