Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@
# Example: https://yourcompany.atlassian.net
JIRA_BASE_URL=https://yourcompany.atlassian.net

# Your Jira account email
# Example: your.email@company.com
JIRA_EMAIL=your.email@company.com
# Authentication type: 'basic' (default) or 'oauth'
# Use 'oauth' if your organization has disabled API token access
JIRA_AUTH_TYPE=basic

# Your Jira API token
# Generate at: https://id.atlassian.com/manage-profile/security/api-tokens
# Click "Create API token" and copy the generated token here
# --- Basic auth (default) ---
# Your Jira account email and API token
# Generate token at: https://id.atlassian.com/manage-profile/security/api-tokens
JIRA_EMAIL=your.email@company.com
JIRA_API_TOKEN=your_api_token_here

# --- OAuth 2.0 (use when JIRA_AUTH_TYPE=oauth) ---
# Create an OAuth 2.0 app at https://developer.atlassian.com
# Add callback URL: http://localhost:7789/callback
# Required scopes: read:jira-user, read:jira-work, write:jira-work, offline_access
# JIRA_OAUTH_CLIENT_ID=your_client_id
# JIRA_OAUTH_CLIENT_SECRET=your_client_secret

# Node environment (development/production)
# Set to 'development' to enable connection testing and debug messages
# Set to 'production' or leave empty for production mode (no connection test, minimal logging)
Expand Down
70 changes: 63 additions & 7 deletions DOCKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ docker pull ghcr.io/freema/mcp-jira-stdio:latest

### Using docker run

**Basic auth:**

```bash
docker run -it --rm \
-e JIRA_BASE_URL="https://your-instance.atlassian.net" \
Expand All @@ -22,6 +24,33 @@ docker run -it --rm \
freema/mcp-jira-stdio:latest
```

**OAuth 2.0:**

OAuth requires a browser callback, so it is not suitable for fully headless Docker usage. If you need OAuth inside Docker, expose the callback port and open the printed URL manually on first run:

```bash
docker run -it --rm \
-p 7789:7789 \
-e JIRA_BASE_URL="https://your-instance.atlassian.net" \
-e JIRA_AUTH_TYPE="oauth" \
-e JIRA_OAUTH_CLIENT_ID="your-client-id" \
-e JIRA_OAUTH_CLIENT_SECRET="your-client-secret" \
freema/mcp-jira-stdio:latest
```

To persist tokens across container restarts, mount the token directory:

```bash
docker run -it --rm \
-p 7789:7789 \
-v "$HOME/.mcp-jira-stdio:/root/.mcp-jira-stdio" \
-e JIRA_BASE_URL="https://your-instance.atlassian.net" \
-e JIRA_AUTH_TYPE="oauth" \
-e JIRA_OAUTH_CLIENT_ID="your-client-id" \
-e JIRA_OAUTH_CLIENT_SECRET="your-client-secret" \
freema/mcp-jira-stdio:latest
```

### Using docker-compose

```bash
Expand All @@ -41,7 +70,7 @@ Images are published for both amd64 and arm64 architectures.

## Integration with Claude Desktop

Add to your Claude Desktop config:
**Basic auth:**

```json
{
Expand All @@ -52,12 +81,9 @@ Add to your Claude Desktop config:
"run",
"-i",
"--rm",
"-e",
"JIRA_BASE_URL",
"-e",
"JIRA_EMAIL",
"-e",
"JIRA_API_TOKEN",
"-e", "JIRA_BASE_URL",
"-e", "JIRA_EMAIL",
"-e", "JIRA_API_TOKEN",
"freema/mcp-jira-stdio:latest"
],
"env": {
Expand All @@ -69,3 +95,33 @@ Add to your Claude Desktop config:
}
}
```

**OAuth 2.0** (with token persistence and callback port):

```json
{
"mcpServers": {
"jira": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-p", "7789:7789",
"-v", "/Users/you/.mcp-jira-stdio:/root/.mcp-jira-stdio",
"-e", "JIRA_BASE_URL",
"-e", "JIRA_AUTH_TYPE",
"-e", "JIRA_OAUTH_CLIENT_ID",
"-e", "JIRA_OAUTH_CLIENT_SECRET",
"freema/mcp-jira-stdio:latest"
],
"env": {
"JIRA_BASE_URL": "https://your-instance.atlassian.net",
"JIRA_AUTH_TYPE": "oauth",
"JIRA_OAUTH_CLIENT_ID": "your-client-id",
"JIRA_OAUTH_CLIENT_SECRET": "your-client-secret"
}
}
}
}
```
142 changes: 126 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,30 @@ A Model Context Protocol (MCP) server for Jira API integration. Enables reading,

The fastest way to add this MCP server to Claude Code:

**Basic auth (API token):**

```bash
claude mcp add jira npx mcp-jira-stdio@latest \
--env JIRA_BASE_URL=https://yourcompany.atlassian.net \
--env JIRA_EMAIL=your-email@example.com \
--env JIRA_API_TOKEN=your-api-token
```

**OAuth 2.0** (for organizations that have disabled API token access):

```bash
claude mcp add jira npx mcp-jira-stdio@latest \
--env JIRA_BASE_URL=https://yourcompany.atlassian.net \
--env JIRA_AUTH_TYPE=oauth \
--env JIRA_OAUTH_CLIENT_ID=your-client-id \
--env JIRA_OAUTH_CLIENT_SECRET=your-client-secret
```

Replace the values with your actual Jira credentials:
- **JIRA_BASE_URL**: Your Jira instance URL (e.g., `https://yourcompany.atlassian.net`)
- **JIRA_EMAIL**: Your Jira account email
- **JIRA_API_TOKEN**: Your Jira API token ([generate here](https://id.atlassian.com/manage-profile/security/api-tokens))
- **JIRA_EMAIL**: Your Jira account email *(basic auth only)*
- **JIRA_API_TOKEN**: Your Jira API token ([generate here](https://id.atlassian.com/manage-profile/security/api-tokens)) *(basic auth only)*
- **JIRA_OAUTH_CLIENT_ID / JIRA_OAUTH_CLIENT_SECRET**: OAuth 2.0 app credentials ([see OAuth setup](#oauth-20-setup)) *(OAuth only)*

That's it! The server will be automatically configured and ready to use.

Expand Down Expand Up @@ -76,11 +89,16 @@ task build

### 3. Jira API Setup

1. Go to your Jira instance settings
2. Create an API token:
- **Jira Cloud**: Go to Account Settings → Security → Create and manage API tokens
- **Jira Server**: Use your username and password (or create an application password)
3. Note your Jira base URL (e.g., `https://yourcompany.atlassian.net`)
Two authentication methods are supported:

#### Basic Auth (API Token) — default

1. Go to [Atlassian API tokens](https://id.atlassian.com/manage-profile/security/api-tokens) and create a token
2. Note your Jira base URL (e.g., `https://yourcompany.atlassian.net`)

#### OAuth 2.0 — for organizations that have disabled API token access

See the [OAuth 2.0 Setup](#oauth-20-setup) section below.

### 4. Configuration

Expand All @@ -95,15 +113,22 @@ cp .env.example .env
task env
```

Example `.env` contents:
**Basic auth** `.env`:

```env
JIRA_BASE_URL=https://your-instance.atlassian.net
JIRA_EMAIL=your-email@example.com
JIRA_API_TOKEN=your-api-token
```

**Note:** Generate your API token at [https://id.atlassian.com/manage-profile/security/api-tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
**OAuth 2.0** `.env`:

```env
JIRA_BASE_URL=https://your-instance.atlassian.net
JIRA_AUTH_TYPE=oauth
JIRA_OAUTH_CLIENT_ID=your-client-id
JIRA_OAUTH_CLIENT_SECRET=your-client-secret
```

### 5. Test Connection

Expand All @@ -121,13 +146,25 @@ task jira:projects

Use the quick install command (recommended):

**Basic auth:**

```bash
claude mcp add jira npx mcp-jira-stdio@latest \
--env JIRA_BASE_URL=https://yourcompany.atlassian.net \
--env JIRA_EMAIL=your-email@example.com \
--env JIRA_API_TOKEN=your-api-token
```

**OAuth 2.0:**

```bash
claude mcp add jira npx mcp-jira-stdio@latest \
--env JIRA_BASE_URL=https://yourcompany.atlassian.net \
--env JIRA_AUTH_TYPE=oauth \
--env JIRA_OAUTH_CLIENT_ID=your-client-id \
--env JIRA_OAUTH_CLIENT_SECRET=your-client-secret
```

#### For Claude Desktop

Add to your Claude Desktop config:
Expand All @@ -136,6 +173,8 @@ Add to your Claude Desktop config:
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- **Linux**: `~/.config/claude/claude_desktop_config.json`

**Basic auth:**

```json
{
"mcpServers": {
Expand All @@ -151,6 +190,24 @@ Add to your Claude Desktop config:
}
```

**OAuth 2.0:**

```json
{
"mcpServers": {
"jira": {
"command": "mcp-jira-stdio",
"env": {
"JIRA_BASE_URL": "https://your-instance.atlassian.net",
"JIRA_AUTH_TYPE": "oauth",
"JIRA_OAUTH_CLIENT_ID": "your-client-id",
"JIRA_OAUTH_CLIENT_SECRET": "your-client-secret"
}
}
}
}
```

#### Alternative: Using npx

```json
Expand Down Expand Up @@ -249,8 +306,9 @@ task inspector:dev

Notes:

- 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`.
- Startup no longer blocks on Jira connectivity. If required env vars are missing the server still starts and lists tools; tool calls will fail with a clear auth error.
- 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.
- When using OAuth, the browser authorization prompt fires on the **first tool call**, not at startup.

### Testing

Expand Down Expand Up @@ -331,6 +389,19 @@ jira_get_visible_projects({
- Check project permissions in Jira
- Ensure you're using the correct Jira instance

**OAuth: "Port 7789 is already in use"**

- Another process is using the callback port. Stop it and retry, or kill it with `lsof -ti:7789 | xargs kill`.

**OAuth: "Jira site not found in accessible resources"**

- The `JIRA_BASE_URL` must match exactly one of the sites returned by Atlassian (e.g., `https://yourcompany.atlassian.net`).
- Delete `~/.mcp-jira-stdio/tokens.json` to force a fresh authorization if credentials changed.

**OAuth: browser does not open automatically**

- The server prints the authorization URL to stderr — copy and open it manually.

**MCP Connection Issues**

- Ensure you're using the built version (`dist/index.js`)
Expand Down Expand Up @@ -377,12 +448,51 @@ npm run inspector

## 🔍 Environment Variables

| Variable | Required | Description | Example |
| ---------------- | -------- | ----------------- | ------------------------------- |
| `JIRA_BASE_URL` | Yes | Jira instance URL | `https://company.atlassian.net` |
| `JIRA_EMAIL` | Yes | Your Jira email | `user@example.com` |
| `JIRA_API_TOKEN` | Yes | Jira API token | `ATxxx...` |
| `NODE_ENV` | No | Environment mode | `development` or `production` |
| Variable | Required | Description | Example |
| --------------------------- | --------------------- | ------------------------------------- | ------------------------------- |
| `JIRA_BASE_URL` | Yes | Jira instance URL | `https://company.atlassian.net` |
| `JIRA_AUTH_TYPE` | No | Auth method: `basic` (default) or `oauth` | `oauth` |
| `JIRA_EMAIL` | Yes *(basic auth)* | Your Jira account email | `user@example.com` |
| `JIRA_API_TOKEN` | Yes *(basic auth)* | Jira API token | `ATxxx...` |
| `JIRA_OAUTH_CLIENT_ID` | Yes *(OAuth)* | OAuth 2.0 app client ID | `abc123` |
| `JIRA_OAUTH_CLIENT_SECRET` | Yes *(OAuth)* | OAuth 2.0 app client secret | `secret...` |
| `NODE_ENV` | No | Environment mode | `development` or `production` |

## 🔑 OAuth 2.0 Setup

Use OAuth 2.0 when your Atlassian organization has disabled personal API token access.

### 1. Create an OAuth 2.0 app

1. Go to [Atlassian Developer Console](https://developer.atlassian.com/console/myapps/) and click **Create**.
2. Choose **OAuth 2.0 integration**.
3. Under **Permissions**, add the **Jira API** and enable these scopes:
- `read:jira-user`
- `read:jira-work`
- `write:jira-work`
- `offline_access` (required for token refresh)
4. Under **Authorization**, add the callback URL: `http://localhost:7789/callback`
5. Copy the **Client ID** and **Client Secret**.

### 2. Configure the server

Set these environment variables (alongside `JIRA_BASE_URL`):

```env
JIRA_AUTH_TYPE=oauth
JIRA_OAUTH_CLIENT_ID=your-client-id
JIRA_OAUTH_CLIENT_SECRET=your-client-secret
```

### 3. First-run authorization

On the first tool call, the server will:

1. Open your browser to the Atlassian authorization page.
2. Ask you to grant access to the app.
3. Store the tokens in `~/.mcp-jira-stdio/tokens.json` (mode `600`).

Subsequent calls use stored tokens and refresh them automatically — no browser interaction needed until the refresh token expires.

## 🤝 Contributing

Expand Down
16 changes: 11 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,26 @@

// Show auth info on startup when configured (skip in DRY_RUN)
if (!isDryRun) {
const hasAuthVars = Boolean(
process.env.JIRA_BASE_URL && process.env.JIRA_EMAIL && process.env.JIRA_API_TOKEN
);
const authType = process.env.JIRA_AUTH_TYPE || 'basic';
const hasAuthVars =
authType === 'oauth'
? Boolean(process.env.JIRA_BASE_URL && process.env.JIRA_OAUTH_CLIENT_ID && process.env.JIRA_OAUTH_CLIENT_SECRET)

Check failure on line 244 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

Replace `process.env.JIRA_BASE_URL·&&·process.env.JIRA_OAUTH_CLIENT_ID·&&·process.env.JIRA_OAUTH_CLIENT_SECRET` with `⏎············process.env.JIRA_BASE_URL·&&⏎············process.env.JIRA_OAUTH_CLIENT_ID·&&⏎············process.env.JIRA_OAUTH_CLIENT_SECRET⏎··········`

Check failure on line 244 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (24.x)

Replace `process.env.JIRA_BASE_URL·&&·process.env.JIRA_OAUTH_CLIENT_ID·&&·process.env.JIRA_OAUTH_CLIENT_SECRET` with `⏎············process.env.JIRA_BASE_URL·&&⏎············process.env.JIRA_OAUTH_CLIENT_ID·&&⏎············process.env.JIRA_OAUTH_CLIENT_SECRET⏎··········`
: Boolean(process.env.JIRA_BASE_URL && process.env.JIRA_EMAIL && process.env.JIRA_API_TOKEN);

Check failure on line 245 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

Replace `process.env.JIRA_BASE_URL·&&·process.env.JIRA_EMAIL·&&·process.env.JIRA_API_TOKEN` with `⏎············process.env.JIRA_BASE_URL·&&·process.env.JIRA_EMAIL·&&·process.env.JIRA_API_TOKEN⏎··········`

Check failure on line 245 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (24.x)

Replace `process.env.JIRA_BASE_URL·&&·process.env.JIRA_EMAIL·&&·process.env.JIRA_API_TOKEN` with `⏎············process.env.JIRA_BASE_URL·&&·process.env.JIRA_EMAIL·&&·process.env.JIRA_API_TOKEN⏎··········`

if (hasAuthVars) {
try {
const auth = validateAuth();
console.error(`🔐 Authenticated as: ${auth.email}`);
if (authType === 'oauth') {
console.error(`🔐 Auth: OAuth 2.0 (token will be obtained on first request)`);
} else {
console.error(`🔐 Authenticated as: ${auth.email}`);
}
console.error(`🌐 Jira instance: ${auth.baseUrl}`);
} catch (error: any) {
console.error('⚠️ Invalid Jira credentials:', error.message);
}
} else {
console.error('⚠️ Jira env vars missing (JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN)');
console.error('⚠️ Jira env vars missing (JIRA_BASE_URL + JIRA_EMAIL/JIRA_API_TOKEN or JIRA_OAUTH_CLIENT_ID/JIRA_OAUTH_CLIENT_SECRET)');

Check failure on line 260 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

Replace `'⚠️··Jira·env·vars·missing·(JIRA_BASE_URL·+·JIRA_EMAIL/JIRA_API_TOKEN·or·JIRA_OAUTH_CLIENT_ID/JIRA_OAUTH_CLIENT_SECRET)'` with `⏎········'⚠️··Jira·env·vars·missing·(JIRA_BASE_URL·+·JIRA_EMAIL/JIRA_API_TOKEN·or·JIRA_OAUTH_CLIENT_ID/JIRA_OAUTH_CLIENT_SECRET)'⏎······`

Check failure on line 260 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (24.x)

Replace `'⚠️··Jira·env·vars·missing·(JIRA_BASE_URL·+·JIRA_EMAIL/JIRA_API_TOKEN·or·JIRA_OAUTH_CLIENT_ID/JIRA_OAUTH_CLIENT_SECRET)'` with `⏎········'⚠️··Jira·env·vars·missing·(JIRA_BASE_URL·+·JIRA_EMAIL/JIRA_API_TOKEN·or·JIRA_OAUTH_CLIENT_ID/JIRA_OAUTH_CLIENT_SECRET)'⏎······`
}
} else {
console.error('🧪 DRY_RUN=1 set — skipping Jira auth check');
Expand Down
Loading
Loading