@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thammarongg/jira-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "MCP server exposing the Jira REST API (boards, sprints, issues, JQL, and a generic passthrough) for Jira Cloud and Data Center",
5
5
  "type": "module",
6
6
  "bin": {
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`, `get_board`, `list_sprints`, `get_sprint`,
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, and these work on team-managed projects too
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