Skip to main content
Glama
README.md
# jira-run-mcp

An internal MCP (Model Context Protocol) server for Jira 8, using SSO session bootstrap via Playwright and HTTP-first tool execution. Uses **stdio transport** — runs as a local subprocess managed by Claude Desktop, Cursor, or any MCP-compatible client.

## Stack

- **TypeScript** — strict, ESM (NodeNext)
- **@modelcontextprotocol/sdk** — MCP server + stdio transport
- **Playwright** — interactive SSO login and session persistence
- **Zod** — config and tool input validation
- **Axios** — Jira REST API HTTP client

## Features

- 🔐 SSO authentication via Playwright (headed browser)
- 💾 Persistent local session (Playwright storage state / cookies)
- 🔍 `jira_get_issue` — fetch a single issue by key
- 🔎 `jira_search_issues` — execute JQL and return a compact issue list
- 🔎 `jira_smart_search` — search by issue key, JQL, or natural-language filters
- 🗓️ `jira_daily_briefing` — fixed Vietnamese project delivery briefing for managers
- ⏱️ `jira_add_worklog` — log work on a Jira issue through Tempo Timesheets
- 📝 `jira_create_issue` — create an issue using issue-type-specific required and optional fields
- 💬 `jira_add_comment` — add plain-text or ADF comments to an issue
- ✏️ `jira_update_comment` / `jira_delete_comment` — update or remove issue comments
- 🔄 `jira_transition_issue` — move an issue through workflow transitions by id or name
- 🧭 `jira_get_create_meta` — inspect static create metadata from `src/jira/constants.ts`
- 🧭 `jira_get_edit_meta` — inspect live editable fields for one issue
- ✏️ `jira_update_issue_fields` — update a curated set of Jira fields safely
- 🛡️ `jira_validate_issue_update` — validate update payloads without writing
- 🛡️ `jira_bulk_update_issue_fields` / `jira_bulk_transition_issues` — bulk operations with explicit `dryRun`
- 🛡️ `jira_preview_create_issue` — build create payloads without POSTing
- 🧾 `jira_get_audit_context` — compact issue audit context for LLM review
- 🔗 `jira_link_issues` — create links between issues
- 🔗 `jira_get_issue_links` / `jira_bulk_link_issues` — inspect or create multiple issue links
- 🧩 `jira_get_subtasks` / `jira_create_subtask` — inspect or create sub-tasks
- 🧬 `jira_clone_issue` — clone an issue with optional field overrides
- 👤 `jira_assign_issue` — assign issues by name or key
- 👥 `jira_find_user` — search Jira users for assignment/collaboration flows
- 📋 `jira_get_transitions` — list currently available transitions for an issue
- 📅 `jira_get_my_worklogs` — list the authenticated user's Tempo worklogs
- 📅 `jira_update_worklog` / `jira_delete_worklog` — correct or remove Tempo worklogs
- 📊 `jira_export_project_timesheet` — export a project's full Tempo timesheet (all members) to xlsx/xls/csv
- 📎 `jira_add_attachment` — upload workspace files as issue attachments
- 📤 `jira_upload_attachment_content` — attach AI-generated content (text, CSV, JSON…) directly without a local file
- 🗂️ `jira_get_projects` / `jira_get_components` / `jira_get_priorities` — discover common Jira metadata
- 🔀 `jira_sync_gitlab_review_defects` — sync GitLab MR review comments into Jira Review Defects (`mrState` or single `mrIid`)
- 🛡️ Clean `SESSION_EXPIRED` / `AUTH_REQUIRED` errors with reauthentication hints
- 🖥️ Three CLI utilities for session management

## Requirements

- Node.js >= 20
- Access to an internal Jira 8 instance (SSO)

---

## Quick Start (End Users)

> No cloning or building required. Install via npm or use `npx` directly.

### Step 1 — Install Playwright Chromium

Required once for the SSO browser login flow:

```bash
npx -y playwright install chromium
```

### Step 2 — Authenticate with Jira

Replace the URL with your actual Jira instance. Choose one method:

**Option A — Interactive SSO (recommended for MFA / multi-step IdP)**

```bash
JIRA_BASE_URL=https://jira.yourcompany.com npx -y -p @cuongph.dev/jira-mcp jira-auth-login
```

A browser window opens. Complete SSO manually. The session is saved to `.jira/session.json` (or `~/.jira/jira-mcp/session.json` for global npx usage).

**Option B — Basic Auth with credentials**

Set both `JIRA_EMAIL` and `JIRA_PASSWORD` in your MCP client `env` block or in `.env` (see `.env.example`). The server tries the stored cookie session first, then HTTP Basic Auth. It does **not** open a browser from MCP tool calls. If both fail, run Option A (`jira-auth-login`). MFA or complex SSO requires Option A.

Verify the session is active:

```bash
JIRA_BASE_URL=https://jira.yourcompany.com npx -y -p @cuongph.dev/jira-mcp jira-auth-check
```

### Step 3 — Add to your MCP client

No separate server process needed — the MCP client spawns and manages the process automatically via stdio.

#### Gemini CLI

```bash
gemini mcp add jira npx -y @cuongph.dev/jira-mcp --env JIRA_BASE_URL=https://jira.yourcompany.com
```

#### Cursor

Edit `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` in your project:

```json
{
  "mcpServers": {
    "jira": {
      "command": "npx",
      "args": ["-y", "@cuongph.dev/jira-mcp"],
      "env": {
        "JIRA_BASE_URL": "https://jira.yourcompany.com",
        "GITLAB_TOKEN": "glpat-xxxx",
        "GITLAB_PROJECTS_JSON": "{\"PROJ\":[{\"name\":\"app-frontend\",\"gitlabBaseUrl\":\"https://gitlab.example.com\",\"projectPath\":\"group/app-frontend\"}]}"
      }
    }
  }
}
```

#### Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "jira": {
      "command": "npx",
      "args": ["-y", "@cuongph.dev/jira-mcp"],
      "env": {
        "JIRA_BASE_URL": "https://jira.yourcompany.com",
        "GITLAB_TOKEN": "glpat-xxxx",
        "GITLAB_PROJECTS_JSON": "{\"PROJ\":[{\"name\":\"app-frontend\",\"gitlabBaseUrl\":\"https://gitlab.example.com\",\"projectPath\":\"group/app-frontend\"}]}"
      }
    }
  }
}
```

> **Tip:** You can omit the `env` block and load settings from:
> - source checkout: `<repo>/.env`
> - npm/npx install: `~/.jira/jira-mcp/.env`
>
> Supported variables include `JIRA_BASE_URL` (required), optional `JIRA_EMAIL` / `JIRA_PASSWORD` for HTTP Basic Auth, `LOG_LEVEL`, and GitLab sync vars (`GITLAB_TOKEN`, `GITLAB_PROJECTS_JSON`, etc.). See `.env.example`.
>
> For GitLab sync, put `GITLAB_TOKEN` and `GITLAB_PROJECTS_JSON` in the MCP `env` block or `.env`. MCP clients do not pass custom top-level blocks like `"config": { ... }` to the server process.

Restart your MCP client after saving the config.

### Session Management

| Command | Description |
|---|---|
| `npx @cuongph.dev/jira-mcp jira-auth-login` | Launch SSO browser flow and save session |
| `npx @cuongph.dev/jira-mcp jira-auth-check` | Validate whether the stored session is alive |
| `npx @cuongph.dev/jira-mcp jira-auth-clear` | Remove the stored session file |

---

## Development Setup

> For contributors and developers working on the source code.

### 1. Clone and install

```bash
git clone <repo-url>
cd jira-run-mcp
npm install
```

### 2. Install Playwright browsers

```bash
npx playwright install chromium
```

### 3. Configure environment

```bash
cp .env.example .env
```

Edit `.env`:

```env
JIRA_BASE_URL=https://jira.yourcompany.com
```

See `.env.example` for all available options.

### 4. Authenticate

**Interactive SSO:**

```bash
npm run jira-auth-login
```

A browser window will open. Complete the SSO login manually. Session is saved to `.jira/session.json`.

**Optional Basic Auth:** add both `JIRA_EMAIL` and `JIRA_PASSWORD` to `.env` (see `.env.example`). On the next MCP tool call the stored cookie is tried first; Basic Auth is used only if the cookie is missing or rejected. MCP never launches Playwright — use `jira-auth-login` for SSO.

```bash
npm run jira-auth-check
```

### 5. Build

```bash
npm run build
```

### 6. Add to MCP client (local build)

Use the local `dist/server.js` instead of the npm package.

| File | Purpose |
|------|---------|
| `.cursor/mcp.json` | Cursor IDE — project-local MCP servers (this repo includes an example) |
| `.agents/mcp.json` | Cursor Agents / subagents — same shape; keep in sync with `.cursor/mcp.json` when both are used |

**Cursor** (`.cursor/mcp.json` in the repo, or `~/.cursor/mcp.json` globally):

```json
{
  "mcpServers": {
    "jira-run-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/jira-run-mcp/dist/server.js"],
      "env": {
        "LOG_LEVEL": "info"
      }
    }
  }
}
```

Add `JIRA_BASE_URL` and optional `JIRA_EMAIL` / `JIRA_PASSWORD` to the `env` block if you do not rely on `.env` alone.

**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "jira": {
      "command": "node",
      "args": ["/absolute/path/to/jira-run-mcp/dist/server.js"]
    }
  }
}
```

> The `.env` file in the project root is loaded automatically — you only need `env` in MCP config for overrides (e.g. `LOG_LEVEL`) or secrets you prefer not to store in `.env`.

### CLI Utilities (dev)

| Command | Description |
|---|---|
| `npm run jira-auth-login` | Launch SSO browser flow and save session |
| `npm run jira-auth-check` | Validate whether the stored session is alive |
| `npm run jira-auth-clear` | Remove the stored session file |

## MCP Tools

### `jira_get_issue`

Fetch a single Jira issue by key.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key, e.g. `PROJ-123` |

**Output:** Key, summary, description, status, assignee, reporter, priority, issue type, created/updated timestamps, URL.

---

### `jira_search_issues`

Execute a JQL query and return a compact issue list.

**Input:**
| Field | Type | Description |
|---|---|---|
| `jql` | `string` | JQL query, e.g. `project = PROJ AND status = Open` |
| `limit` | `number` | Max results (1–50, default 10) |

**Output:** Total count + list of issues (key, summary, status, assignee, priority, updated, URL).

---

### `jira_daily`

Return a read-only daily Jira project report with counts, status/progress distribution, due and overdue work, weighted progress, blocker/risk signals, analysis, and navigation hints.

**Input:** `projectKey` (required), optional `date` (`yyyy-MM-dd`, local today by default), `maxIssues` (1-200, default 50), and `maxBlockers` (1-50, default 20).

**Docs:** [`docs/tools/jira_daily.md`](docs/tools/jira_daily.md)

---

### `jira_daily_briefing`

Turn the authoritative daily project data into a fixed-format Vietnamese briefing for a project manager. The tool is read-only and limits issue evidence lookups to the highest-impact concerns.

**Input:** `projectKey` (required), optional `date`, `maxConcerns` (1-20, default 5), and `audience` (default `project manager`).

**Docs:** [`docs/tools/jira_daily_briefing.md`](docs/tools/jira_daily_briefing.md)

---

### `jira_smart_search`

Search by issue key, explicit JQL, or natural-language filters.

**Input:**
| Field | Type | Description |
|---|---|---|
| `query` | `string` | Issue key, JQL, or natural-language text, e.g. `open bugs assigned to me` |
| `mode` | `string` | Optional: `auto`, `jql`, or `smart` |
| `project` | `string` | Optional Jira project key, e.g. `DNIEM` |
| `limit` | `number` | Max results (1-50, default 10) |

**Output:** Direct issue lookup or compact search results with generated/executed JQL.

---

### `jira_create_issue`

Create a Jira issue for a specific issue type.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueTypeId` | `string` | Jira issue type ID from `src/jira/constants.ts` |
| `fields` | `object` | Jira create fields keyed by standard field names or `customfield_*` IDs; `fields.description` accepts `string` or raw ADF |

**Output:** Confirmation with created issue key, summary, issue type, and browser URL.

---

### `jira_sync_gitlab_review_defects`

Sync top-level GitLab MR review comments into Jira **Review Defect** issues. Requires `GITLAB_TOKEN` and GitLab project links. Preferred MCP setup is `GITLAB_PROJECTS_JSON` in the MCP `env` block (stringified JSON). Fallback map file path is `.jira/gitlab-projects.json` in a source checkout, or `~/.jira/jira-mcp/gitlab-projects.json` when running `@cuongph.dev/jira-mcp` via npm/npx. Each mapping entry must include a non-empty repository `name`; generated summaries use `[Review Code][<name>][MR !<IID>] ...` to identify the repository.

**Input:**
| Field | Type | Description |
|---|---|---|
| `projectKey` | `string` | Jira project key mapped in `GITLAB_PROJECTS_JSON` or the GitLab projects file |
| `mrState` | `string` | Optional: `opened`, `merged` (default), or `closed` |
| `mrIid` | `number` | Optional: process one MR only (ignores `mrState`) |
| `dryRun` | `boolean` | Default `true` — preview only; `false` to create issues |
| `userOverrides` | `object` | GitLab username → Jira username/email when lookup fails |

**Docs:** [`docs/tools/jira_sync_gitlab_review_defects.md`](docs/tools/jira_sync_gitlab_review_defects.md)

---

### `jira_add_worklog`

Log work on a Jira issue through Tempo Timesheets.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `timeSpent` | `string` | Duration using `Nd`, `Nh`, `Nm` tokens |
| `startDate` | `string` | Optional work date in `yyyy-MM-dd` format |
| `comment` | `string` | Optional worklog comment |
| `process` | `string` | Optional Tempo Process attribute |
| `typeOfWork` | `string` | Optional Tempo Type Of Work attribute |

**Output:** Confirmation with issue details, worker, date, duration, and Tempo worklog IDs.

---

### `jira_add_comment`

Add a comment to a Jira issue.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `body` | `string \| object` | Comment body as plain text or raw ADF |

**Output:** Confirmation with comment id and browser URL.

---

### `jira_transition_issue`

Transition a Jira issue to a target workflow state.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `transitionId` | `string` | Optional Jira workflow transition id |
| `transitionName` | `string` | Optional transition name resolved from current available transitions |
| `comment` | `string \| object` | Optional plain text or ADF comment |
| `fields` | `object` | Optional curated field updates sent with the transition |

Provide exactly one of `transitionId` or `transitionName`.

**Output:** Confirmation with issue key, transition id, and browser URL.

---

### `jira_get_create_meta`

Return static create metadata for supported Jira issue types.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueTypeId` | `string` | Optional issue type id to narrow the result |

**Output:** Required fields, optional fields, and known option sets from `src/jira/constants.ts`.

---

### `jira_get_edit_meta`

Return live editable fields for a specific issue.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |

**Output:** Field IDs, labels, required flags, schema types, and allowed values returned by Jira.

---

### `jira_update_issue_fields`

Update a curated set of fields on an existing issue.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `fields` | `object` | Curated set of updateable field ids/values |

**Output:** Confirmation with updated field ids and browser URL.

---

### `jira_validate_issue_update`

Validate an issue update without writing.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `fields` | `object` | Curated update fields to validate |

**Output:** Validation status, normalized update payload, and any fields not editable according to live edit metadata.

---

### `jira_bulk_update_issue_fields`

Update fields on multiple issues with explicit dry-run control.

**Input:**
| Field | Type | Description |
|---|---|---|
| `dryRun` | `boolean` | Required. `true` previews only; `false` applies updates |
| `issues` | `array` | 1-25 items with `issueKey` and `fields` |

**Output:** Per-issue status table. Later items continue after per-issue failures.

---

### `jira_bulk_transition_issues`

Transition multiple issues with explicit dry-run control.

**Input:**
| Field | Type | Description |
|---|---|---|
| `dryRun` | `boolean` | Required. `true` resolves/previews only; `false` applies transitions |
| `issues` | `array` | 1-25 items with `issueKey`, exactly one of `transitionId`/`transitionName`, optional `comment` and `fields` |

**Output:** Per-issue status table with resolved transition ids. Later items continue after per-issue failures.

---

### `jira_preview_create_issue`

Build and validate a create issue payload without sending it to Jira.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueTypeId` | `string` | Jira issue type ID from `src/jira/constants.ts` |
| `fields` | `object` | Jira create fields keyed by standard field names or `customfield_*` IDs |

**Output:** Normalized Jira create payload JSON.

---

### `jira_get_audit_context`

Fetch compact context for LLM review of one issue.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `includeComments` | `boolean` | Include recent comments, default true |
| `maxComments` | `number` | Max comments, 1-100, default 20 |

**Output:** Issue summary, key fields, description, issue links, subtasks, and optional comments.

---

### `jira_link_issues`

Create a Jira issue link between two issues.

**Input:**
| Field | Type | Description |
|---|---|---|
| `inwardIssueKey` | `string` | Source issue key |
| `outwardIssueKey` | `string` | Target issue key |
| `linkType` | `string` | Jira link type name |
| `comment` | `string \| object` | Optional plain text or ADF comment |

**Output:** Confirmation with issue keys, link type, and link id.

---

### `jira_get_issue_links`

List issue links for a Jira issue.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |

**Output:** Link direction, type, relationship, linked issue key, summary, and status.

---

### `jira_get_subtasks`

List subtasks for a Jira issue.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Parent Jira issue key |

**Output:** Subtask key, summary, status, assignee, priority, and URL.

---

### `jira_create_subtask`

Create a subtask under a parent issue.

**Input:**
| Field | Type | Description |
|---|---|---|
| `parentIssueKey` | `string` | Parent Jira issue key |
| `issueTypeId` | `string` | Jira subtask issue type id for the project |
| `fields` | `object` | Jira create fields; `parent` and `issuetype` are injected |

**Output:** Confirmation with parent key, created subtask key, and browser URL.

---

### `jira_clone_issue`

Clone an issue by copying core fields.

**Input:**
| Field | Type | Description |
|---|---|---|
| `sourceIssueKey` | `string` | Source Jira issue key |
| `summaryPrefix` | `string` | Optional prefix, default `Clone of` |
| `fields` | `object` | Optional field overrides for the created issue |

**Output:** Confirmation with source key, cloned issue key, and browser URL.

---

### `jira_bulk_link_issues`

Create multiple issue links sequentially.

**Input:**
| Field | Type | Description |
|---|---|---|
| `links` | `array` | 1-25 link requests with `inwardIssueKey`, `outwardIssueKey`, `linkType`, optional `comment` |

**Output:** Per-link status table. If one link fails, later links are still attempted and the MCP result is marked as an error.

---

### `jira_assign_issue`

Assign a Jira issue to a user.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `assigneeName` | `string` | Optional Jira username/name |
| `assigneeKey` | `string` | Optional Jira internal user key |

**Output:** Confirmation with assignee and browser URL.

---

### `jira_find_user`

Search Jira users for assignment and collaboration flows.

**Input:**
| Field | Type | Description |
|---|---|---|
| `query` | `string` | Username, display name, or search text |
| `maxResults` | `number` | Max results (1-50, default 10) |

**Output:** Display name, username, user key, active flag, and email if Jira exposes it.

---

### `jira_get_transitions`

List the currently available workflow transitions for an issue.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |

**Output:** Transition ids, names, and destination statuses.

---

### `jira_update_comment`

Update an existing issue comment.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `commentId` | `string` | Jira comment id |
| `body` | `string \| object` | Replacement body as plain text or raw ADF |

**Output:** Confirmation with issue key, comment id, and browser URL.

---

### `jira_delete_comment`

Delete an issue comment by id.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `commentId` | `string` | Jira comment id |

**Output:** Confirmation with issue key and deleted comment id.

---

### `jira_get_my_worklogs`

List the authenticated user's Tempo worklogs.

**Input:**
| Field | Type | Description |
|---|---|---|
| `dateFrom` | `string` | Optional start date in `yyyy-MM-dd` format |
| `dateTo` | `string` | Optional end date in `yyyy-MM-dd` format |

**Output:** Tempo worklog ids, issue keys, dates, durations, and comments.

---

### `jira_update_worklog`

Update a Tempo worklog by id.

**Input:**
| Field | Type | Description |
|---|---|---|
| `worklogId` | `string` | Tempo worklog id |
| `timeSpent` | `string` | Optional duration using `Nd`, `Nh`, `Nm` tokens |
| `startDate` | `string` | Optional date in `yyyy-MM-dd` format |
| `comment` | `string` | Optional updated comment |
| `process` | `string` | Optional Tempo Process attribute |
| `typeOfWork` | `string` | Optional Tempo Type Of Work attribute |

**Output:** Confirmation with updated Tempo id, issue key, date, and duration.

---

### `jira_delete_worklog`

Delete a Tempo worklog by id.

**Input:**
| Field | Type | Description |
|---|---|---|
| `worklogId` | `string` | Tempo worklog id |

**Output:** Confirmation with deleted Tempo id.

---

### `jira_export_project_timesheet`

Export a project's full Tempo timesheet — worklogs from **all** members, not a hand-picked worker list — using Tempo's own export flow. The file is written to the `ATTACHMENT_WORKSPACE` downloads folder.

**Input:**
| Field | Type | Description |
|---|---|---|
| `projectKey` | `string` | Jira project key, e.g. `PROJ` |
| `dateFrom` | `string` | Start date in `yyyy-MM-dd` format |
| `dateTo` | `string` | End date inclusive in `yyyy-MM-dd` format |
| `format` | `"xlsx" \| "xls" \| "csv"` | Optional, default `xlsx` (sent to Tempo as `ooxml`) |

**Output:** Project key, period, resolved format, absolute file path, and file size.

**Docs:** [`docs/tools/jira_export_project_timesheet.md`](docs/tools/jira_export_project_timesheet.md)

---

### `jira_add_attachment`

Upload a local file from the allowed workspace directory to an issue.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `filePath` | `string` | Path to a file inside the `ATTACHMENT_WORKSPACE` directory |

Files outside the configured `ATTACHMENT_WORKSPACE` are rejected. Set the env var to restrict the allowed root directory.

**Output:** Uploaded attachment ids, filenames, sizes, and issue URL.

---

### `jira_upload_attachment_content`

Upload in-memory content as a Jira issue attachment — no local file needed.

**Input:**
| Field | Type | Description |
|---|---|---|
| `issueKey` | `string` | Jira issue key |
| `filename` | `string` | Filename with extension, e.g. `report.md`, `data.csv` |
| `content` | `string` | Plain text (utf8) or base64-encoded content |
| `encoding` | `"utf8" \| "base64"` | Default `utf8` |
| `mimeType` | `string` | Optional MIME type override; inferred from extension if omitted |

**Output:** Uploaded attachment id, filename, size, MIME type, and issue URL.

---

### `jira_get_projects`

List Jira projects visible to the authenticated user.

**Input:** none.

**Output:** Project keys, names, ids, and browser URLs.

---

### `jira_get_components`

List components for a Jira project.

**Input:**
| Field | Type | Description |
|---|---|---|
| `projectKey` | `string` | Jira project key |

**Output:** Component ids, names, and descriptions.

---

### `jira_get_priorities`

List Jira priorities configured in the instance.

**Input:** none.

**Output:** Priority ids, names, and descriptions.

## Project Structure

```
src/
├── server.ts              # MCP server entry point
├── config.ts              # Env var validation (Zod)
├── errors.ts              # Typed error classes & factories
├── types.ts               # Shared TypeScript types
├── auth/
│   ├── session-store.ts   # Read/write/clear session.json
│   ├── session-manager.ts # Session validation against Jira
│   └── playwright-auth.ts # Headed SSO browser flow
├── jira/
│   ├── endpoints.ts       # URL builders (REST API v2)
│   ├── mappers.ts         # Raw payload → typed output shapes
│   ├── adf.ts             # ADF type interfaces + low-level builders
│   ├── body-normalizer.ts # normalizeJiraBody (markdown/plain/adf → ADF)
│   ├── markdown-to-adf.ts # Markdown AST → ADF converter (remark)
│   ├── create-meta.ts     # Static issue create metadata helpers
│   ├── create-issue.ts    # Create-issue validation and payload helpers
│   ├── edit-meta.ts       # Live issue edit metadata normalization
│   ├── user-search.ts     # User search normalization
│   ├── transition-resolution.ts # Transition name resolution
│   ├── update-issue.ts    # Curated field update normalization
│   └── http-client.ts     # Cookie-authenticated Jira HTTP client
├── tools/
│   ├── add-attachment.ts  # jira_add_attachment handler
│   ├── add-comment.ts     # jira_add_comment handler
│   ├── bulk-link-issues.ts # jira_bulk_link_issues handler
│   ├── clone-issue.ts     # jira_clone_issue handler
│   ├── create-subtask.ts  # jira_create_subtask handler
│   ├── get-issue.ts       # jira_get_issue handler
│   ├── get-issue-links.ts # jira_get_issue_links handler
│   ├── get-subtasks.ts    # jira_get_subtasks handler
│   ├── find-user.ts       # jira_find_user handler
│   ├── get-components.ts  # jira_get_components handler
│   ├── get-create-meta.ts # jira_get_create_meta handler
│   ├── get-edit-meta.ts   # jira_get_edit_meta handler
│   ├── get-my-worklogs.ts # jira_get_my_worklogs handler
│   ├── get-priorities.ts  # jira_get_priorities handler
│   ├── get-projects.ts    # jira_get_projects handler
│   ├── get-transitions.ts # jira_get_transitions handler
│   ├── link-issues.ts     # jira_link_issues handler
│   ├── assign-issue.ts    # jira_assign_issue handler
│   ├── delete-comment.ts  # jira_delete_comment handler
│   ├── delete-worklog.ts  # jira_delete_worklog handler
│   ├── export-project-timesheet.ts # jira_export_project_timesheet handler
│   ├── search-issues.ts   # jira_search_issues handler
│   ├── smart-search.ts    # jira_smart_search handler
│   ├── add-worklog.ts     # jira_add_worklog handler
│   ├── transition-issue.ts # jira_transition_issue handler
│   ├── update-comment.ts  # jira_update_comment handler
│   ├── update-issue-fields.ts # jira_update_issue_fields handler
│   ├── update-worklog.ts  # jira_update_worklog handler
│   └── create-issue.ts    # jira_create_issue handler
├── cli/
│   ├── auth-login.ts      # jira-auth-login entry point
│   ├── auth-check.ts      # jira-auth-check entry point
│   └── auth-clear.ts      # jira-auth-clear entry point
└── tests/                 # Unit tests (Vitest)
```

## Authentication Flow

```
Operator
  │
  ▼
npm run jira-auth-login
  │
  ├── Playwright opens browser (headed)
  ├── Operator completes SSO manually
  ├── storageState saved → .jira/session.json
  └── Session validated immediately
  
MCP Tool Call
  │
  ├── Load .jira/session.json
  ├── Validate against /rest/api/2/myself
  ├── Extract cookies → build HTTP request
  └── Return normalized Jira data
  
Session Expired?
  │
  └── Returns [SESSION_EXPIRED] error
      → "Run: npm run jira-auth-login"
```

## Development Commands

```bash
# Type check
npx tsc --noEmit

# Run tests
npm test

# Watch mode
npm run test:watch

# Build for production
npm run build

# Run directly (dev mode, reads .env)
npm run dev
```

### Testing with MCP Inspector

[MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the official GUI tool for interactively testing MCP servers — call tools, inspect inputs/outputs, and debug without an AI client.

**Option A — dev mode (no build required):**

```bash
npx @modelcontextprotocol/inspector tsx src/server.ts
```

`tsx` compiles TypeScript on-the-fly. Changes are picked up on every Inspector session restart without a rebuild step.

**Option B — built bundle:**

```bash
npm run build
npx @modelcontextprotocol/inspector node dist/server.js
```

Both options read the `.env` file in the project root automatically.

After the Inspector starts, open the URL it prints (usually `http://localhost:5173`) in your browser, select a tool from the sidebar, fill in the inputs, and click **Run**.

## Error Codes

| Code | Meaning |
|---|---|
| `AUTH_REQUIRED` | No session file found — run `jira-auth-login` |
| `SESSION_EXPIRED` | Session exists but Jira rejected it — rerun `jira-auth-login` |
| `JIRA_HTTP_ERROR` | Unexpected HTTP error from Jira REST API |
| `JIRA_RESPONSE_ERROR` | Jira returned an unexpected response shape |
| `CONFIG_ERROR` | Invalid or missing environment variable |
| `INVALID_INPUT` | Tool input or issue-type-specific field set is invalid |

## Security Notes

- `.env` and `.jira/session.json` are **git-ignored** and must never be committed.
- Session cookies give full Jira access as the authenticated user — treat them like passwords.
- The session file is stored locally only; no remote storage is involved.