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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
{
"name": "bitwarden-atlassian-tools",
"source": "./plugins/bitwarden-atlassian-tools",
"version": "2.8.2",
"version": "2.9.0",
"description": "Atlassian access via MCP server with deep Jira issue research skill, JQL search, Confluence pages, CQL search, attachments, and opt-in Jira write tools"
},
{
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A curated collection of plugins for AI-assisted development at Bitwarden. Enable
| [bitwarden-tech-lead](plugins/bitwarden-tech-lead/) | 3.1.0 | Tech lead for technical planning, architecture coherence, and surfacing patterns to Technical Strategy Ideas |
| [bitwarden-shepherd](plugins/bitwarden-shepherd/) | 1.0.1 | Champion of a technical strategy β€” shepherds a TSI through evaluation into the funnel, then through to adoption |
| [bitwarden-adr-tools](plugins/bitwarden-adr-tools/) | 1.0.0 | Architecture Decision Record tools: checking designs and plans against the ADR catalog, locating and summarizing ADRs |
| [bitwarden-atlassian-tools](plugins/bitwarden-atlassian-tools/) | 2.8.2 | Atlassian access via MCP server with deep Jira issue research skill and opt-in Jira write tools |
| [bitwarden-atlassian-tools](plugins/bitwarden-atlassian-tools/) | 2.9.0 | Atlassian access via MCP server with deep Jira issue research skill and opt-in Jira write tools |
| [bitwarden-code-review](plugins/bitwarden-code-review/) | 2.7.0 | Autonomous code review agent following Bitwarden engineering standards with GitHub integration |
| [bitwarden-delivery-tools](plugins/bitwarden-delivery-tools/) | 3.5.2 | Delivery lifecycle skills: initiative funnel navigation, work transitions, architectural judgment, feature flags, commits, PRs, preflight, labeling, Jira ticket filing |
| [bitwarden-designer](plugins/bitwarden-designer/) | 0.1.0 | Product designer persona: Code of Conduct and 30/60/90 critique, critique facilitation; dispatches into bitwarden-design-tools |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bitwarden-atlassian-tools",
"version": "2.8.2",
"version": "2.9.0",
"description": "Atlassian access via MCP server with deep Jira issue research skill, JQL search, Confluence pages, CQL search, attachments, and opt-in Jira write tools",
"author": {
"name": "Bitwarden"
Expand Down
6 changes: 6 additions & 0 deletions plugins/bitwarden-atlassian-tools/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to the Bitwarden Atlassian Tools plugin will be documented i
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.9.0] - 2026-10-30

### Added

- `transition_issue` moves an issue to a status named by the caller, such as `In Progress` or `Done`, and finds the workflow transition that reaches it, so no transition ids or workflow knowledge are needed up front. An issue already in the target status is left alone, which keeps the tool safe to call repeatedly from automation. When no transition reaches the target, it lists the statuses that are reachable instead. Like the other write tools it defaults to a dry run, needs `ATLASSIAN_JIRA_WRITE_TOKEN` only for the live transition, and reads the status back afterwards to confirm where the issue landed.

## [2.8.2] - 2026-10-30

### Fixed
Expand Down
16 changes: 9 additions & 7 deletions plugins/bitwarden-atlassian-tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Atlassian access via a custom MCP server providing Jira issue retrieval, JQL search, Confluence page reading, CQL search, and attachment downloads.

Read access is the default and always available. Jira write access (creating work items, links, and comments) is **opt-in per install**: `create_issue`, `link_issues`, and `add_issue_comment` are always listed and their dry-run preview always works, but without `ATLASSIAN_JIRA_WRITE_TOKEN` a live write refuses to execute. Confluence remains read-only with no write path.
Read access is the default and always available. Jira write access (creating work items, links, and comments, and transitioning issues) is **opt-in per install**: `create_issue`, `link_issues`, `add_issue_comment`, and `transition_issue` are always listed and their dry-run preview always works, but without `ATLASSIAN_JIRA_WRITE_TOKEN` a live write refuses to execute. Confluence remains read-only with no write path.

## Installation

Expand All @@ -17,7 +17,7 @@ export ATLASSIAN_EMAIL="your-email@company.com"
export ATLASSIAN_JIRA_READ_ONLY_TOKEN="your-jira-scoped-token"
export ATLASSIAN_CONFLUENCE_READ_ONLY_TOKEN="your-confluence-scoped-token"

# Optional β€” enables the Jira write tools (create_issue, link_issues, add_issue_comment).
# Optional β€” enables the Jira write tools (create_issue, link_issues, add_issue_comment, transition_issue).
# Omit to keep this install read-only.
export ATLASSIAN_JIRA_WRITE_TOKEN="your-jira-write-scoped-token"
```
Expand Down Expand Up @@ -88,28 +88,29 @@ The Jira Agile (Software) endpoints behind `list_boards`, `get_sprints`, and `ge

### Jira (write, requires `ATLASSIAN_JIRA_WRITE_TOKEN`)

All three tools default to a dry run that returns the exact payload without sending it. A live write requires an explicit `dryRun: false`. Dry runs need no write token.
All four tools default to a dry run that returns the exact payload without sending it. A live write requires an explicit `dryRun: false`. Dry runs need no write token.

| Tool | Purpose |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create_issue` | Create one work item in any project. Carries no project-specific field knowledge: pass anything beyond the common core through `fields`, keyed by field id from `get_create_fields` |
| `link_issues` | Link two work items. For a dependency, takes `blockerKey` and `blockedKey` and maps them onto Jira's inward/outward sides internally so the direction cannot be inverted |
| `add_issue_comment` | Add a comment to an existing issue. The body is markdown: emphasis, links, inline and fenced code, lists, and tables all render |
| `transition_issue` | Move an issue to a status by name. Finds the transition that reaches it, does nothing if the issue is already there, and lists the reachable statuses if none does |

The classic `write:jira-work` scope alone covers all three write tools β€” it grants creating and editing issues and posting comments as the user. For a **scoped (granular)** write token, add these instead, on top of the read scopes above:
The classic `write:jira-work` scope alone covers all four write tools β€” it grants creating and editing issues and posting comments as the user. For a **scoped (granular)** write token, add these instead, on top of the read scopes above:

| Scope | Required for |
| ----------------------------- | ------------------------------------------------------------------ |
| `read:issue:jira` | `create_issue` |
| `read:issue:jira-software` | `create_issue` |
| `write:issue:jira` | `create_issue`, `link_issues` |
| `write:issue:jira` | `create_issue`, `link_issues`, `transition_issue` |
| `write:issue:jira-software` | `create_issue`, `link_issues` |
| `write:issue-link:jira` | `link_issues` |
| `write:comment:jira` | `create_issue`, `link_issues` (scope-only), `add_issue_comment` |
| `write:comment.property:jira` | `create_issue` (required even though it never sends a comment) |
| `write:attachment:jira` | `create_issue` (required even though it never sends an attachment) |

Grant the **whole** set, not a subset β€” a token holding only some of them fails every write with `401 Unauthorized; scope does not match`. For a scoped write token covering all three write tools, that's:
Grant the **whole** set, not a subset β€” a token holding only some of them fails every write with `401 Unauthorized; scope does not match`. For a scoped write token covering all four write tools, that's:

```
read:issue:jira
Expand All @@ -124,7 +125,7 @@ write:issue:jira

`get_create_fields` needs no additional scope. It calls the createmeta endpoints, which the existing read-only token already satisfies.

Token scope is separate from Jira project permission. Creating also requires the **Create Issues** permission in the target project, linking requires **Link Issues**, and commenting requires **Add Comments**. In a project where the user lacks Create Issues, Jira answers `You cannot create issues in this project`, which `get_create_fields` reports as an ordinary result rather than an error.
Token scope is separate from Jira project permission. Creating also requires the **Create Issues** permission in the target project, linking requires **Link Issues**, commenting requires **Add Comments**, and transitioning requires **Transition Issues**. In a project where the user lacks Create Issues, Jira answers `You cannot create issues in this project`, which `get_create_fields` reports as an ordinary result rather than an error.

A leaked write token permits more than any one tool uses: `write:comment.property:jira` and `write:attachment:jira` are granted only because Atlassian rejects a narrower scope set, so the token can also set comment properties and add attachments across every project the user can reach, in addition to the comments `add_issue_comment` itself sends. Treat this token as higher-blast-radius than the read-only token and rotate it accordingly.

Expand All @@ -149,6 +150,7 @@ The MCP tools are available as `mcp__bitwarden-atlassian__<tool_name>`. Examples
- Search Confluence: `mcp__bitwarden-atlassian__search_confluence_cql` with `cql: "space = EN AND text ~ \"search term\""`
- Preview a ticket before creating it: `mcp__bitwarden-atlassian__create_issue` with `project: "PM"`, `issueType: "Story"`, `summary: "Add CSV export to the item list"` β€” omit `dryRun` (defaults to `true`) to get the payload back without creating anything
- Preview a comment before posting it: `mcp__bitwarden-atlassian__add_issue_comment` with `issueIdOrKey: "PROJ-123"`, `body: "Looks good to me, one nit: the **guard** is inverted."` β€” omit `dryRun` (defaults to `true`) to get the payload back without posting anything
- Preview a status change: `mcp__bitwarden-atlassian__transition_issue` with `issueIdOrKey: "PROJ-123"`, `status: "In Progress"` β€” omit `dryRun` (defaults to `true`) to see which transition would apply without moving the issue

## Skills

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import getCreateFields from "./tools/get-create-fields.js";
import createIssue from "./tools/create-issue.js";
import linkIssues from "./tools/link-issues.js";
import addIssueComment from "./tools/add-issue-comment.js";
import transitionIssue from "./tools/transition-issue.js";

const tools: ToolDefinition[] = [
getIssue,
Expand All @@ -76,6 +77,7 @@ const tools: ToolDefinition[] = [
createIssue,
linkIssues,
addIssueComment,
transitionIssue,
];

async function main() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
JiraRemoteLink,
JiraCreateMetaIssueType,
JiraCreateMetaField,
JiraTransition,
} from "./types.js";

export class JiraClient {
Expand Down Expand Up @@ -403,6 +404,33 @@ export class JiraClient {
});
}

/**
* List the transitions available from an issue's current status, as this
* user. Transitions the user lacks permission for, or whose conditions fail,
* are omitted by Jira.
*/
async getTransitions(issueIdOrKey: string): Promise<JiraTransition[]> {
const response = await this.client.get<{ transitions: JiraTransition[] }>(
`${this.API_BASE}/issue/${issueIdOrKey}/transitions`,
);

return response.data.transitions;
}

/**
* Move an issue through a workflow transition. Jira answers 204 with no
* body on success.
*/
async transitionIssue(
issueIdOrKey: string,
transitionId: string,
): Promise<void> {
await this.client.post(
`${this.API_BASE}/issue/${issueIdOrKey}/transitions`,
{ transition: { id: transitionId } },
);
}

// ── Create-screen Metadata (read-only, works with either token) ─────

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ export interface JiraStatus {
};
}

/**
* A workflow transition available from an issue's current status, as returned
* by GET /issue/{key}/transitions. `name` is the transition's own label (e.g.
* "Start Progress"); `to.name` is the status it lands on.
*/
export interface JiraTransition {
id: string;
name: string;
to: JiraStatus;
}

export interface JiraPriority {
id: string;
name: string;
Expand Down
Loading
Loading