From jira-writer
Read, search, create, and update Jira Cloud tickets — fetch issue details, search with JQL, list projects, look up users, and write rich content with automatic Mermaid diagram embedding
npx claudepluginhub yunidbauza/claude-kit --plugin jira-writerThis skill uses the workspace's default tool permissions.
This skill enables reading, searching, creating, and modifying Jira Cloud tickets. You can fetch issue details, search with JQL queries, list projects and issue types, look up users, and view issue transitions. When writing content that includes Mermaid diagram code blocks, diagrams are automatically converted to PNG images and embedded in the ticket description.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
This skill enables reading, searching, creating, and modifying Jira Cloud tickets. You can fetch issue details, search with JQL queries, list projects and issue types, look up users, and view issue transitions. When writing content that includes Mermaid diagram code blocks, diagrams are automatically converted to PNG images and embedded in the ticket description.
This skill activates contextually when:
```mermaid ``` code blocks that need embeddingBefore executing Jira operations, verify these dependencies:
This skill uses a hybrid approach with REST API as the primary method:
| Priority | Method | When Used |
|---|---|---|
| 1st | REST API | Always tried first (requires JIRA_DOMAIN + JIRA_API_KEY) |
| 2nd | Atlassian MCP | Fallback when REST fails or isn't configured |
Decision logic:
IF JIRA_DOMAIN and JIRA_API_KEY configured:
TRY REST API first
IF REST fails:
FALL BACK to MCP (if available)
ELSE:
USE MCP directly (if available)
IF MCP unavailable:
REPORT error with setup instructions
REST API is the recommended primary method for all operations.
Check: Verify environment variable JIRA_API_KEY exists.
Format: email:api_token (plain text, NOT base64 encoded)
export JIRA_API_KEY="your-email@domain.com:your_api_token"IMPORTANT: Store the raw email:api_token string. The upload scripts handle base64 encoding internally.
Check: Verify environment variable JIRA_DOMAIN exists (e.g., company.atlassian.net).
If missing: Ask user to provide their Jira domain.
The Atlassian MCP provides Jira API access as a fallback when REST API is unavailable.
Check: Attempt to use any mcp__atlassian__* tool.
If unavailable and REST also unavailable:
Neither REST API nor Atlassian MCP is configured.
For REST API (recommended):
1. Generate an API token at https://id.atlassian.com/manage-profile/security/api-tokens
2. Set environment variables:
export JIRA_DOMAIN="company.atlassian.net"
export JIRA_API_KEY="your-email@domain.com:your_api_token"
For MCP fallback:
1. Install the MCP from the Atlassian marketplace
2. Configure authentication in your Claude Code MCP settings
3. Restart Claude Code
Use a content-based approach for API selection:
| Content Type | API to Use | Reason |
|---|---|---|
| Headings, paragraphs, text | REST or MCP | Both work |
| Bullet lists, numbered lists | REST or MCP | Both work |
| Tables | REST or MCP | Both work |
| Code blocks | REST or MCP | Both work |
| Bold, italic, links | REST or MCP | Both work |
Checkboxes (- [ ], - [x]) | REST API | MCP converts to escaped text, not interactive taskList |
| Images/Mermaid diagrams | REST API | MCP cannot handle mediaSingle nodes |
| External media | REST API | MCP cannot embed media |
| Attachments | REST API only | MCP cannot upload files |
Decision logic:
SCAN content for:
- Checkbox patterns: `- [ ]` or `- [x]` or `* [ ]` or `* [x]`
- Mermaid blocks: ```mermaid
- Image references or embedded media
- Attachments to upload
IF any complex content found:
USE REST API (required, no fallback)
ELSE:
USE REST API first
IF REST fails: FALL BACK to MCP
The mmdc command converts Mermaid syntax to PNG.
Check (deferred until first diagram):
which mmdc
If missing:
Mermaid CLI (mmdc) not found. Install it?
Run: npm install -g @mermaid-js/mermaid-cli
[If user declines, skip diagram processing with warning]
Cache result: After first check, remember mmdc availability for session.
| Missing | Behavior |
|---|---|
| REST API credentials | Fall back to MCP; if MCP unavailable, stop with setup instructions |
| MCP | REST API handles everything (no impact if REST configured) |
| Both REST and MCP | Skill cannot function; stop with setup instructions |
| JIRA_API_KEY only | Text operations via MCP; diagrams/checkboxes skipped with warning |
| mmdc | Diagrams skipped with warning; offer installation |
The following scripts automate operations (located in scripts/ directory):
| Script | Purpose | Input Format |
|---|---|---|
jira-api-wrapper.sh | PRIMARY interface - use this for all operations | Positional arguments |
jira-rest-api.sh | Low-level API access (advanced use only) | Raw JSON |
IMPORTANT: Always use jira-api-wrapper.sh for Jira operations. It accepts user-friendly positional arguments and handles JSON construction internally. Only use jira-rest-api.sh directly when you need to pass custom raw JSON payloads.
jira-api-wrapper.sh (USE THIS)
# Create issue - accepts positional args
./scripts/jira-api-wrapper.sh create_issue PROJECT_KEY TYPE "Summary" "Description"
./scripts/jira-api-wrapper.sh create_issue PROJ Task "Fix login bug" "Users cannot login"
# Update issue
./scripts/jira-api-wrapper.sh update_issue PROJ-123 '{"fields":{"summary":"New title"}}'
# Get issue
./scripts/jira-api-wrapper.sh get_issue PROJ-123
# Search with JQL
./scripts/jira-api-wrapper.sh search_jql "project = PROJ AND status = Open"
# Get projects and issue types
./scripts/jira-api-wrapper.sh get_projects
./scripts/jira-api-wrapper.sh get_issue_types PROJECT_KEY
# Add comment
./scripts/jira-api-wrapper.sh add_comment PROJ-123 "This is a comment"
# Transitions
./scripts/jira-api-wrapper.sh get_transitions PROJ-123
./scripts/jira-api-wrapper.sh transition_issue PROJ-123 TRANSITION_ID
# Upload attachment
./scripts/jira-api-wrapper.sh upload_attachment PROJ-123 /path/to/file.png
jira-rest-api.sh (Low-level - advanced use only)
# Only use when you need raw JSON control
./scripts/jira-rest-api.sh jira_create_issue '{"fields":{"project":{"key":"PROJ"},"issuetype":{"name":"Task"},"summary":"Title"}}'
./scripts/jira-rest-api.sh jira_get_issue PROJ-123
./scripts/jira-rest-api.sh jira_update_issue PROJ-123 '{"fields":{...}}'
test-jira-connection.sh
./scripts/test-jira-connection.sh
# Tests API connectivity and returns recommendation
check-prerequisites.sh
./scripts/check-prerequisites.sh
# Returns JSON with status of all dependencies
jira-mermaid-upload.sh
./scripts/jira-mermaid-upload.sh <issue_key> <mermaid_file_or_code> [filename]
# Converts mermaid to PNG and uploads to Jira
# Returns: { "attachment_id": "...", "content_url": "...", "filename": "..." }
jira-mermaid-batch-upload.sh
./scripts/jira-mermaid-batch-upload.sh <issue_key> '<json_array_of_diagrams>'
# Processes multiple diagrams in one call
When creating new issues:
Check available issue types for a project:
./scripts/jira-api-wrapper.sh get_issue_types PROJECT_KEY
# Or via MCP: mcp__atlassian__getJiraProjectIssueTypesMetadata with projectIdOrKey
Follow these steps when creating or updating Jira tickets:
IF issue key mentioned in request (e.g., "update PROJ-123"):
USE that issue
ELSE IF issue already in conversation context (previously fetched/discussed):
USE that issue
ELSE IF multiple issues in context:
ASK user which one to target
ELSE:
ASK user: "Which Jira issue should I update, or should I create a new one?"
Content sources (in priority order):
SCAN content for pattern: ```mermaid ... ```
IF mermaid blocks found:
EXTRACT each block
QUEUE for conversion (Step 4)
ELSE:
SKIP to Step 5
For each mermaid block:
4a. CREATE temp file
TEMP_DIR=$(mktemp -d)
Write mermaid code to $TEMP_DIR/diagram-N.mmd
4b. VALIDATE syntax
Run: mmdc -i $TEMP_DIR/diagram-N.mmd -o /dev/null 2>&1
IF error:
REPORT: "Diagram N has syntax error: [error message]"
SKIP this diagram, continue with others
4c. CONVERT to PNG
Run: mmdc -i $TEMP_DIR/diagram-N.mmd -o $TEMP_DIR/diagram-N.png \
--backgroundColor white --theme neutral --scale 2
IF error:
REPORT conversion error
SKIP this diagram
4d. UPLOAD attachment (REST API required)
Use: ./scripts/jira-api-wrapper.sh upload_attachment $ISSUE_KEY $TEMP_DIR/diagram-N.png
Or directly:
POST to: https://$JIRA_DOMAIN/rest/api/3/issue/$ISSUE_KEY/attachments
Headers:
Authorization: Basic $JIRA_API_KEY
X-Atlassian-Token: no-check
Body: multipart/form-data with file
CAPTURE attachment ID and content URL from response
Content URL format: https://$JIRA_DOMAIN/rest/api/3/attachment/content/<id>
IF error:
IF 401/403: STOP, report auth error
IF 404: STOP, report issue not found
ELSE: Retry once, then skip with warning
4e. TRACK mapping
Store: mermaid_block_index -> attachment_id
SCAN content for complex elements:
has_checkboxes = content contains `- [ ]` or `- [x]` or `* [ ]` or `* [x]`
has_mermaid = mermaid blocks were found in Step 3
has_images = content contains image references
requires_rest_api = has_checkboxes OR has_mermaid OR has_images
IF requires_rest_api:
USE REST API only (no MCP fallback for complex content)
PROCEED to Step 5a (Build full ADF manually)
ELSE:
TRY REST API first
IF REST fails: FALL BACK to MCP
PROCEED to Step 6
CONVERT markdown content to ADF nodes:
- Headings -> heading nodes
- Paragraphs -> paragraph nodes
- Bullet lists -> bulletList/listItem nodes
- Numbered lists -> orderedList/listItem nodes
- Checkboxes -> taskList/taskItem nodes (see below)
- Code blocks -> codeBlock nodes
- Tables -> table/tableRow/tableHeader/tableCell nodes
- Bold/italic -> text with marks
- Links -> text with link mark
FOR each checkbox pattern:
CONVERT to taskItem:
- `- [ ] text` -> state: "TODO"
- `- [x] text` -> state: "DONE"
- Generate unique localId (UUID) for each taskList and taskItem
FOR each mermaid block position:
REPLACE with mediaSingle node:
{
"type": "mediaSingle",
"attrs": { "layout": "center" },
"content": [{
"type": "media",
"attrs": {
"type": "external",
"url": "https://$JIRA_DOMAIN/rest/api/3/attachment/content/<attachment_id>"
}
}]
}
Choose API based on content complexity (determined in Step 5):
For new issues:
# Try REST API first
./scripts/jira-api-wrapper.sh create_issue PROJECT_KEY "Task" "Summary" "Description"
# If response indicates MCP fallback needed:
# Use mcp__atlassian__createJiraIssue with:
# - projectKey
# - issueTypeName (default: "Task" if not specified by user)
# - summary
# - description (markdown - MCP converts to ADF)
Issue type mapping:
For existing issues:
# Try REST API first
./scripts/jira-api-wrapper.sh update_issue PROJ-123 '{"description": "..."}'
# If response indicates MCP fallback needed:
# Use mcp__atlassian__editJiraIssue with:
# - description (markdown - MCP converts to ADF)
Content with checkboxes, images, or mermaid diagrams requires REST API.
For new issues:
1. CREATE issue via REST API (or MCP if REST unavailable) with summary only:
./scripts/jira-api-wrapper.sh create_issue PROJECT_KEY "Task" "Summary"
2. UPDATE description via REST API:
curl -X PUT \
-H "Authorization: Basic $(echo -n $JIRA_API_KEY | base64)" \
-H "Content-Type: application/json" \
-d '{"fields":{"description": <ADF_DOCUMENT>}}' \
"https://$JIRA_DOMAIN/rest/api/3/issue/<key>"
For existing issues:
DETERMINE update mode from user request:
DEFAULT (append):
FETCH current description via REST API or MCP
PARSE existing ADF content
APPEND new ADF nodes to existing content array
UPDATE via REST API (PUT /rest/api/3/issue/<key>)
REPLACE (user says "replace", "overwrite"):
UPDATE via REST API with new ADF only
INSERT (user specifies location like "after section X"):
FETCH current description
PARSE ADF to find target section
INSERT new content at specified position
UPDATE via REST API
PREPEND (user says "at the beginning", "at the top"):
FETCH current description
PREPEND new ADF nodes before existing
UPDATE via REST API
REST API update format:
curl -X PUT \
-H "Authorization: Basic $(echo -n "$JIRA_API_KEY" | base64)" \
-H "Content-Type: application/json" \
-d '{
"fields": {
"description": {
"version": 1,
"type": "doc",
"content": [...]
}
}
}' \
"https://$JIRA_DOMAIN/rest/api/3/issue/$ISSUE_KEY"
On update failure with uploaded attachments:
ROLLBACK:
FOR each uploaded attachment_id:
DELETE via REST API: DELETE /rest/api/3/attachment/{id}
REPORT: "Failed to update description. Cleaned up uploaded attachments."
REMOVE temp directory and files:
rm -rf $TEMP_DIR
REPORT success:
"Updated PROJ-123 with [description of changes]"
IF diagrams embedded:
"Embedded N diagram(s)"
Quick reference for Atlassian Document Format nodes.
{
"version": 1,
"type": "doc",
"content": [ /* array of block nodes */ ]
}
Heading:
{
"type": "heading",
"attrs": { "level": 2 },
"content": [{ "type": "text", "text": "Heading Text" }]
}
Paragraph:
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Paragraph text" }]
}
Bullet List:
{
"type": "bulletList",
"content": [{
"type": "listItem",
"content": [{
"type": "paragraph",
"content": [{ "type": "text", "text": "Item text" }]
}]
}]
}
Ordered List:
{
"type": "orderedList",
"content": [{ "type": "listItem", "content": [...] }]
}
Task List (Checkboxes):
{
"type": "taskList",
"attrs": { "localId": "unique-uuid-1" },
"content": [
{
"type": "taskItem",
"attrs": { "localId": "unique-uuid-2", "state": "TODO" },
"content": [{ "type": "text", "text": "Unchecked item" }]
},
{
"type": "taskItem",
"attrs": { "localId": "unique-uuid-3", "state": "DONE" },
"content": [{ "type": "text", "text": "Checked item" }]
}
]
}
Task List states:
"state": "TODO" - Unchecked checkbox (markdown: - [ ])"state": "DONE" - Checked checkbox (markdown: - [x])IMPORTANT: Each taskList and taskItem requires a unique localId (UUID format). Generate with uuidgen or similar.
Code Block:
{
"type": "codeBlock",
"attrs": { "language": "python" },
"content": [{ "type": "text", "text": "code here" }]
}
Media (embedded image from attachment):
{
"type": "mediaSingle",
"attrs": { "layout": "center" },
"content": [{
"type": "media",
"attrs": {
"type": "external",
"url": "https://your-domain.atlassian.net/rest/api/3/attachment/content/ATTACHMENT_ID"
}
}]
}
Table:
{
"type": "table",
"attrs": { "isNumberColumnEnabled": false, "layout": "default" },
"content": [
{
"type": "tableRow",
"content": [
{
"type": "tableHeader",
"attrs": {},
"content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Header 1" }] }]
},
{
"type": "tableHeader",
"attrs": {},
"content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Header 2" }] }]
}
]
},
{
"type": "tableRow",
"content": [
{
"type": "tableCell",
"attrs": {},
"content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Cell 1" }] }]
},
{
"type": "tableCell",
"attrs": {},
"content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Cell 2" }] }]
}
]
}
]
}
Table node types:
table - Container for the entire tabletableRow - A row in the tabletableHeader - Header cell (first row typically)tableCell - Regular data cellBold:
{ "type": "text", "text": "bold", "marks": [{ "type": "strong" }] }
Italic:
{ "type": "text", "text": "italic", "marks": [{ "type": "em" }] }
Code:
{ "type": "text", "text": "code", "marks": [{ "type": "code" }] }
Link:
{ "type": "text", "text": "link text", "marks": [{ "type": "link", "attrs": { "href": "https://..." } }] }
| Layout | Behavior |
|---|---|
center | Centered, original size (recommended) |
wide | Wider than text column |
full-width | Full page width |
align-start | Left-aligned |
align-end | Right-aligned |
Reference for Mermaid diagram generation.
| Type | Syntax Start | Use Case |
|---|---|---|
| Flowchart | graph TD or graph LR | Process flows, decision trees |
| Sequence | sequenceDiagram | API calls, interactions |
| Class | classDiagram | Object models, relationships |
| State | stateDiagram-v2 | State machines, lifecycles |
| ER | erDiagram | Database schemas |
| Gantt | gantt | Project timelines |
| Pie | pie | Proportions, distributions |
| Git | gitGraph | Branch strategies |
| Mindmap | mindmap | Concept organization |
| Timeline | timeline | Chronological events |
mmdc -i input.mmd -o output.png \
--backgroundColor white \
--theme neutral \
--scale 2
| Option | Value | Rationale |
|---|---|---|
--backgroundColor | white | Matches Jira's white background |
--theme | neutral | Clean, professional look |
--scale | 2 | High resolution for retina |
Alternative themes: default, forest, dark
Generate descriptive filenames based on:
auth-flow-diagram.pngsequence-diagram.pngdiagram-1.png, diagram-2.pngBefore conversion, validate syntax:
mmdc -i input.mmd -o /dev/null 2>&1
If exit code non-zero, report the error and skip the diagram.
How to handle errors at each stage.
| Stage | Error | Action |
|---|---|---|
| Prerequisites | REST unavailable, MCP unavailable | STOP; provide setup instructions |
| Prerequisites | REST auth failed | WARN; try MCP fallback |
| Prerequisites | JIRA_DOMAIN missing | ASK user to provide |
| Prerequisites | mmdc missing | OFFER install; if declined, skip diagrams |
| Mermaid validation | Syntax error | REPORT details; skip diagram, continue others |
| PNG conversion | mmdc fails | REPORT error; skip diagram |
| Attachment upload | 401/403 | STOP; report auth error, check API key |
| Attachment upload | 404 | STOP; issue doesn't exist |
| Attachment upload | Other error | RETRY once; if fails, skip with warning |
| Description update | REST error | TRY MCP fallback (for simple content); ROLLBACK attachments; report error |
| Section detection | Section not found | ASK user for clarification |
When description update fails after attachments were uploaded:
FOR each uploaded attachment_id:
curl -X DELETE \
-H "Authorization: Basic $JIRA_API_KEY" \
"https://$JIRA_DOMAIN/rest/api/3/attachment/$attachment_id"
REPORT to user:
"Failed to update the issue description. I've cleaned up the uploaded
diagram attachments to avoid orphaned files. Error: [details]"
When processing multiple diagrams and some fail:
CONTINUE processing remaining diagrams
REPORT at end:
"Embedded 2 of 3 diagrams successfully.
Diagram 2 skipped due to syntax error: [error details]"
Always provide actionable information:
1. JIRA_API_KEY format
email:api_token in env varexport JIRA_API_KEY=$(echo -n "email:token" | base64)export JIRA_API_KEY="email:token"2. ADF Media nodes for attachments
type: "external" with the attachment content URLtype: "file" with attachment ID (requires Media API UUID)https://$JIRA_DOMAIN/rest/api/3/attachment/content/<attachment_id>3. MCP cannot handle complex ADF content
- [ ] to escaped text in bulletList, NOT interactive taskList4. MCP cannot update description with raw ADF
curl -X PUT -H "Authorization: Basic <encoded>" \
-H "Content-Type: application/json" \
-d '{"fields":{"description":<adf_document>}}' \
"https://$JIRA_DOMAIN/rest/api/3/issue/<key>"
5. Checkbox ADF requires unique localIds
taskList and taskItem node requires a unique localId attribute"localId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890")uuidgen command or equivalent6. Attachment upload requires REST API
jira-mermaid-upload.sh script handles this7. Issue not found errors
8. REST API vs MCP fallback
Common usage patterns for this skill.
User request:
"Create a ticket in PROJECT for the new authentication feature. Include a sequence diagram showing the OAuth flow."
Skill execution:
User request:
"Update PROJ-123 with the content from feature-spec.md"
Skill execution:
feature-spec.mdUser request:
"Add an ER diagram showing the user tables to the current ticket"
Skill execution:
User request:
"Replace the description of PROJ-456 with this new spec"
Skill execution:
description field (full replacement)User request:
"Insert the architecture diagram after the 'Technical Overview' section in PROJ-789"
Skill execution:
User request:
"Create a ticket for implementing user login with these acceptance criteria:
- User can enter email and password
- Invalid credentials show error message
- Remember me checkbox works"
Skill execution:
- [ ], - [x]) -> requires REST API{
"type": "taskList",
"attrs": { "localId": "<uuid>" },
"content": [
{ "type": "taskItem", "attrs": { "localId": "<uuid>", "state": "TODO" }, "content": [...] },
{ "type": "taskItem", "attrs": { "localId": "<uuid>", "state": "TODO" }, "content": [...] },
{ "type": "taskItem", "attrs": { "localId": "<uuid>", "state": "DONE" }, "content": [...] }
]
}
User request:
"Create a ticket to refactor the database connection pool"
Skill execution:
./scripts/jira-api-wrapper.sh create_issue PROJECT "Task" "Refactor database connection pool" "Description..."
mcp__atlassian__createJiraIssue with:
User request:
"Show me the details of PROJ-123"
Skill execution:
./scripts/jira-api-wrapper.sh get_issue PROJ-123User request:
"Find all open bugs assigned to me in PROJECT"
Skill execution:
project = PROJECT AND issuetype = Bug AND status != Done AND assignee = currentUser()./scripts/jira-api-wrapper.sh search_jql "project = PROJECT AND issuetype = Bug AND status != Done AND assignee = currentUser()"User request:
"What Jira projects do I have access to?"
Skill execution:
./scripts/jira-api-wrapper.sh get_projects