Bidirectional sync plugin between Backlog.md and Jira via MCP Atlassian server
- Overview
- Architecture
- Features
- Prerequisites
- Installation
- Configuration
- Usage
- Commands
- Troubleshooting
- Development
- License
The Backlog.md Jira plugin enables seamless bidirectional synchronization between Backlog.md tasks and Jira issues. Built as a standalone plugin following the zero-coupling architecture introduced in PR #394, it operates entirely through public APIs without requiring any modifications to Backlog.md core.
- 🔄 Bidirectional Sync: Keep Backlog tasks and Jira issues in sync automatically
- ✅ Acceptance Criteria: Sync acceptance criteria with full checked/unchecked state
- 📊 Status Mapping: Flexible status mapping with project-specific overrides
- 🔍 Conflict Detection: Field-level conflict detection with multiple resolution strategies
- 📝 Field Mapping: Sync titles, descriptions, assignees, labels, and custom fields
- 🔐 Secure: Uses MCP (Model Context Protocol) for secure Jira access
- 📦 Standalone: Zero modifications to Backlog.md core - fully independent plugin
The plugin follows a zero-coupling architecture that ensures complete separation from Backlog.md core:
-
Public APIs Only:
- Uses Backlog CLI for all Backlog operations
- Uses MCP Atlassian server for all Jira operations
- No direct file manipulation or internal API calls
-
External State Management:
- File-based storage in
.backlog-jira/stores all plugin state - Task frontmatter stores Jira metadata (jira_key, jira_last_sync, etc.)
- Snapshots stored as JSON files in
.backlog-jira/snapshots/ - Operations log in
.backlog-jira/ops-log.jsonl - No modifications to Backlog.md data structures
- File-based storage in
-
3-Way Merge:
- Stores base snapshots for both Backlog and Jira sides
- Enables intelligent conflict detection by comparing current state to last known state
- Supports multiple conflict resolution strategies
-
Standalone CLI:
- Separate
backlog-jiracommand namespace - Independent npm package installation
- Can be installed, updated, or removed without affecting Backlog.md
- Separate
┌──────────────┐ ┌─────────────────┐ ┌──────────┐
│ Backlog.md │◄────────┤ backlog-jira ├────────►│ Jira │
│ Tasks │ CLI │ Plugin │ MCP │ Issues │
└──────────────┘ └─────────────────┘ └──────────┘
│
▼
┌───────────────────┐
│ File-Based Store │
│ - Frontmatter │
│ - Snapshots │
│ - Ops Log │
└───────────────────┘
Key Components:
- Integration Layer: Wrappers for Backlog CLI and MCP Atlassian client
- State Store: File-based storage using task frontmatter, JSON snapshots, and JSONL operations log
- Sync Engine: Handles push, pull, and bidirectional sync with conflict resolution
- Configuration System: JSON-based configuration with project-specific overrides
- Logger: Pino-based logging with secret redaction and structured output
The plugin uses file-based storage in .backlog-jira/:
- Task Frontmatter: Jira metadata (jira_key, jira_last_sync, jira_sync_state) stored directly in task files
- Snapshots: Payload snapshots for 3-way merge stored in
.backlog-jira/snapshots/<task-id>-<side>.json - Operations Log: Audit log in
.backlog-jira/ops-log.jsonl(JSONL format)
Benefits:
- ✅ Git-friendly (all metadata is version controlled)
- ✅ Human-readable (no binary database files)
- ✅ Single source of truth (metadata lives with the task)
- ✅ No external dependencies (no SQLite required)
- ✅ Task-Issue Mapping: Create and manage mappings between Backlog tasks and Jira issues
- ✅ Push (Backlog → Jira): Push changes from Backlog to Jira
- ✅ Pull (Jira → Backlog): Pull changes from Jira to Backlog
- ✅ Bidirectional Sync: Intelligent 3-way merge with conflict detection
- ✅ Acceptance Criteria Sync: Full support for AC with checked/unchecked state
- ✅ Status Mapping: Flexible status mapping with project overrides
- ✅ Field-Level Conflicts: Detect conflicts at field level (title, description, status, etc.)
- ✅ Multiple Conflict Strategies: prefer-backlog, prefer-jira, prompt, manual
- ✅ Dry Run Mode: Preview changes without applying them
- ✅ Batch Operations: Sync multiple tasks at once with
--allflag
- ✅ Watch Mode: Automatic polling-based sync with configurable intervals
- ✅ Environment Validation: Comprehensive
doctorcommand checking all dependencies - ✅ Performance Optimization: Parallel batch processing for large datasets (100 tasks < 30s)
- ✅ Rate Limit Handling: Exponential backoff and graceful error handling
- ✅ Cross-Platform Support: Works on Linux, macOS, and Windows via Bun runtime
- ✅ Built-in MCP Server:
backlog-jira mcp startcommand with DNS configuration support
- Web UI Integration: Pull/Push buttons in browser interface
- Custom Field Mapping: User-defined field mapping rules
- Webhooks: Real-time sync triggered by Jira webhooks
Before installing the plugin, ensure you have:
-
Backlog.md CLI installed and configured
- Install from: https://github.com/MrLesk/Backlog.md
- Verify:
backlog --version
-
MCP Atlassian Server configured
- Install the MCP Atlassian server for Jira access
- Configure with your Jira credentials (see Configuration section)
-
Bun Runtime (recommended) or Node.js 20+
- Bun:
curl -fsSL https://bun.sh/install | bash - Or Node.js: https://nodejs.org/
- Bun:
-
Active Backlog.md Project
- Navigate to a directory with
backlog/folder - Or initialize one:
backlog init
- Navigate to a directory with
npm install -g backlog-jira# Clone the repository
git clone https://github.com/YOUR-USERNAME/Backlog.md-jira-plugin.git
cd Backlog.md-jira-plugin
# Install dependencies
npm install
# Build the CLI
npm run build
# Link globally (optional)
npm link
# Or run directly
./dist/cli.js --helpbacklog-jira --version
backlog-jira doctorIn your Backlog.md project directory:
backlog-jira initThis creates .backlog-jira/ directory with:
config.json- Configuration filesnapshots/- Snapshot directory for 3-way mergeops-log.jsonl- Operations audit log.gitignore- Excludes sensitive files
The plugin requires MCP Atlassian server for Jira access. Configure it with environment variables or in your MCP settings:
Environment Variables (.env or shell):
# Jira Configuration
JIRA_BASE_URL=https://your-domain.atlassian.net
JIRA_USER_EMAIL=your-email@example.com
JIRA_API_TOKEN=your-jira-api-token
# Optional: MCP Server Path (if not in PATH)
MCP_ATLASSIAN_PATH=/path/to/mcp-atlassianGetting Jira API Token:
- Go to https://id.atlassian.com/manage-profile/security/api-tokens
- Click "Create API token"
- Copy the token and save it securely
Edit .backlog-jira/config.json:
{
"jira": {
"baseUrl": "https://your-domain.atlassian.net",
"projectKey": "PROJ",
"issueType": "Task",
"jqlFilter": ""
},
"backlog": {
"statusMapping": {
"To Do": ["To Do", "Open", "Backlog"],
"In Progress": ["In Progress", "In Development"],
"Done": ["Done", "Closed", "Resolved"]
}
},
"sync": {
"conflictStrategy": "prompt",
"enableAnnotations": false,
"watchInterval": 60
}
}| Option | Description | Example |
|---|---|---|
baseUrl |
Your Jira instance URL | https://company.atlassian.net |
projectKey |
Default Jira project key | PROJ, DEV, SUPPORT |
issueType |
Default issue type for new issues | Task, Story, Bug |
jqlFilter |
Optional JQL filter for queries | labels = backend |
| Option | Description | Details |
|---|---|---|
statusMapping |
Maps Backlog statuses to Jira | See Status Mapping Guide |
projectOverrides |
Project-specific status mappings | Override per Jira project |
| Option | Description | Values |
|---|---|---|
conflictStrategy |
Default conflict resolution | prompt, prefer-backlog, prefer-jira, manual |
enableAnnotations |
Add sync metadata to tasks | true, false |
watchInterval |
Watch mode check interval (seconds) | 60, 300 |
backlog-jira doctor
backlog-jira connectThe doctor command checks:
- ✅ Bun runtime version
- ✅ Backlog CLI availability
- ✅ Configuration file validity
- ✅ Database permissions
- ✅ Backlog.md project detection
- ✅ Git repository status
The connect command verifies:
- ✅ Backlog CLI connectivity
- ✅ MCP Atlassian server connectivity
- ✅ Jira API credentials
-
Create a mapping between Backlog task and Jira issue:
backlog-jira map
Follow the interactive prompts to map tasks to issues.
-
Push changes from Backlog to Jira:
backlog-jira push task-123
-
Pull changes from Jira to Backlog:
backlog-jira pull task-123
-
Bidirectional sync with conflict resolution:
backlog-jira sync task-123
Option A: Using create-issue command (Recommended)
# List unmapped Backlog tasks
backlog task list --plain
# Create Jira issue directly from Backlog task
backlog-jira create-issue task-123
# Or preview first with dry-run
backlog-jira create-issue task-123 --dry-run
# Create with custom issue type
backlog-jira create-issue task-123 --issue-type BugOption B: Using map + push
# Map a task to create a new Jira issue
backlog-jira map
# Select task → Choose "Create new Jira issue"
# Push the task to create the issue
backlog-jira push task-123# Map existing task to existing Jira issue
backlog-jira map
# Select task → Choose "Link to existing issue" → Enter issue key
# Sync bidirectionally
backlog-jira sync task-123# Configure JQL filter in .backlog-jira/config.json
# Or use command-line JQL:
# Preview what would be imported
backlog-jira pull --import --jql "project = MYPROJ" --dry-run
# Import all issues from project
backlog-jira pull --import --jql "project = MYPROJ"
# Import only open issues
backlog-jira pull --import --jql "project = MYPROJ AND status = 'Open'"
# Import issues from multiple projects
backlog-jira pull --import --jql "project IN (PROJ1, PROJ2) AND created >= -30d"# Sync all mapped tasks
backlog-jira sync --all
# Push all changes with dry-run first
backlog-jira push --all --dry-run
backlog-jira push --all
# Pull all updates from Jira
backlog-jira pull --all
# Import new issues and update existing ones
backlog-jira pull --import# Sync with automatic conflict resolution
backlog-jira sync task-123 --strategy prefer-backlog
# Sync with interactive prompts
backlog-jira sync task-123 --strategy prompt
# Preview conflicts without resolving
backlog-jira sync task-123 --dry-runInitialize plugin configuration and database.
backlog-jira initCreates:
.backlog-jira/config.json.backlog-jira/snapshots/.backlog-jira/ops-log.jsonl.backlog-jira/.gitignore
Run health checks on your environment.
backlog-jira doctorChecks:
- Runtime (Bun/Node.js)
- Backlog CLI installation
- Configuration validity
- Database connectivity
- Project structure
- Git status
Verify connectivity to Backlog and Jira.
backlog-jira connectTests:
- Backlog CLI execution
- MCP Atlassian server connection
- Jira API authentication
- Project access permissions
Create and manage task-to-issue mappings.
# Interactive mapping (auto-discover or manual selection)
backlog-jira map interactive
backlog-jira map i # Short alias
# Auto-map tasks by title similarity
backlog-jira map auto
backlog-jira map auto --dry-run # Preview without creating mappings
backlog-jira map auto --min-score 0.8 # Set minimum similarity thresholdWhen you know the exact Jira issue key, use map link for fast direct mapping:
# Link a task to a Jira issue by key
backlog-jira map link task-123 PROJ-456
# Overwrite existing mapping with --force
backlog-jira map link task-123 PROJ-789 --forceWhen to use map link:
- You already know the Jira issue key
- Faster than interactive selection
- Useful for scripting and automation
- Good for bulk mapping operations
Example workflow:
# Create a new task
backlog task create "Implement OAuth authentication"
# Output: Created task-125
# Link it to existing Jira issue
backlog-jira map link task-125 AUTH-42
# Start syncing
backlog-jira sync task-125# View current mappings
backlog-jira map --list
# Remove a mapping
backlog-jira map --remove task-123View sync status and recent operations.
# Overall status
backlog-jira status
# Status for specific task
backlog-jira status task-123
# Show last N operations
backlog-jira status --history 20Create a new Jira issue from an unmapped Backlog task.
# Create issue for a task
backlog-jira create-issue task-123
# Preview what would be created
backlog-jira create-issue task-123 --dry-run
# Create with custom issue type
backlog-jira create-issue task-123 --issue-type Bug
backlog-jira create-issue task-123 --issue-type Story
backlog-jira create-issue task-123 --issue-type EpicWhat this command does:
- Validates that the task exists in Backlog
- Validates that the task is not already mapped to a Jira issue
- Reads all task metadata (title, description, status, assignee, labels, priority, AC)
- Maps Backlog priority to Jira priority (High/Medium/Low → High/Medium/Low)
- Merges acceptance criteria into Jira description format
- Creates the Jira issue via MCP
jira_create_issuetool - Creates the mapping between task and Jira issue
- Stores initial snapshots for 3-way merge conflict detection
- Updates task frontmatter with Jira metadata (jiraKey, jiraUrl, jiraSyncState)
Flags:
--dry-run: Preview the issue that would be created without actually creating it--issue-type <type>: Override the default issue type from config (e.g., Bug, Story, Epic)
Success output:
✅ Successfully created Jira issue TEST-123 for task task-324
Error cases:
- Task not found:
❌ Failed to create Jira issue: Task task-999 not found in Backlog - Already mapped:
❌ Failed to create Jira issue: Task task-123 is already mapped to Jira issue TEST-100 - No project configured:
❌ Failed to create Jira issue: Jira project key not configured in .backlog-jira/config.json
Push changes from Backlog to Jira.
# Push single task
backlog-jira push task-123
# Push multiple tasks
backlog-jira push task-123 task-124 task-125
# Push all mapped tasks
backlog-jira push --all
# Dry run (preview changes)
backlog-jira push task-123 --dry-run
# Force push (ignore conflicts)
backlog-jira push task-123 --forceWhat gets pushed:
- Title → Summary
- Description → Description
- Status → Status (with transitions)
- Assignee → Assignee
- Labels → Labels
- Acceptance Criteria → Embedded in description
Pull changes from Jira to Backlog.
# Pull single task
backlog-jira pull task-123
# Pull multiple tasks
backlog-jira pull task-123 task-124
# Pull all mapped tasks
backlog-jira pull --all
# Dry run (preview changes)
backlog-jira pull task-123 --dry-run
# Force pull (ignore conflicts)
backlog-jira pull task-123 --forceWhat gets pulled:
- Summary → Title
- Description → Description (AC extracted)
- Status → Status
- Assignee → Assignee
- Labels → Labels
- Acceptance Criteria → Parsed from description
Import unmapped Jira issues as new Backlog tasks:
# Import issues using JQL filter from config
backlog-jira pull --import
# Import with custom JQL filter
backlog-jira pull --import --jql "project = PROJ AND status = 'Open'"
# Preview import without creating tasks
backlog-jira pull --import --dry-run
# Import and force-update any conflicts
backlog-jira pull --import --forceImport behavior:
- Fetches Jira issues using JQL filter (from
--jqlflag, config.json, or JIRA_PROJECT env var) - Creates new Backlog tasks for unmapped issues
- Automatically creates mappings
- Syncs all Jira fields (title, description, status, assignee, labels, priority)
- Extracts and converts Acceptance Criteria from Jira description
- Also pulls updates for already-mapped tasks found in the JQL results
- Without
--importflag, only pulls already-mapped tasks (preserves existing behavior)
JQL Configuration Priority:
--jqlcommand-line option (highest priority)jqlFilterin.backlog-jira/config.json- Default:
project = JIRA_PROJECT ORDER BY created DESC
Bidirectional sync with conflict resolution.
# Sync single task
backlog-jira sync task-123
# Sync with strategy
backlog-jira sync task-123 --strategy prefer-backlog
backlog-jira sync task-123 --strategy prefer-jira
backlog-jira sync task-123 --strategy prompt
backlog-jira sync task-123 --strategy manual
# Sync all tasks
backlog-jira sync --all
# Dry run
backlog-jira sync task-123 --dry-runConflict Strategies:
prefer-backlog: Use Backlog value when conflict detectedprefer-jira: Use Jira value when conflict detectedprompt: Ask user for each conflict (interactive)manual: Skip conflicts, log them for manual resolution
View task/issue details and sync status.
# View task details
backlog-jira view task-123
# View with Jira issue details
backlog-jira view task-123 --with-jira
# View sync history
backlog-jira view task-123 --historyWatch for changes and auto-sync.
# Start watch mode
backlog-jira watch
# Watch with specific interval
backlog-jira watch --interval 300
# Watch specific tasks
backlog-jira watch task-123 task-124Problem: The terminal is not configured for UTF-8 encoding, causing Unicode characters to display incorrectly.
Solution:
-
Check your terminal encoding:
locale | grep UTF -
Set UTF-8 encoding (add to
~/.bashrcor~/.zshrc):export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8
-
For Windows users:
- Use Windows Terminal or WSL2 with UTF-8 support
- In CMD/PowerShell, run:
chcp 65001 - Or use Git Bash which supports UTF-8 by default
-
Alternative - ASCII-only mode (if UTF-8 is not available):
# Set environment variable to disable Unicode characters export FORCE_ASCII=1 backlog-jira configure
Note: The plugin uses Unicode characters (›, ✔, …) for better user experience in modern terminals. These require UTF-8 support.
Solution:
# Verify Backlog CLI is installed
which backlog
backlog --version
# If not found, install from:
# https://github.com/MrLesk/Backlog.mdSolution:
# Check MCP server is configured
echo $JIRA_BASE_URL
echo $JIRA_USER_EMAIL
# Verify API token is set
echo $JIRA_API_TOKEN
# Test connection manually
backlog-jira connectProblem: Jira workflow doesn't allow the status transition.
Solution:
- Check available transitions in Jira UI
- Update status mapping in
.backlog-jira/config.json - See Status Mapping Guide
# Enable debug logging to see available transitions
LOG_LEVEL=debug backlog-jira push task-123Solution:
- Verify AC format in Backlog:
- [ ] #1 Criterion text - Check Jira description format:
Acceptance Criteria:section - See AC Sync Guide
# View task with AC
backlog task task-123 --plain
# Check sync logs
cat .backlog-jira/logs/sync.log | grep "AC"Solution:
- Run with
--dry-runto preview conflicts - Choose appropriate strategy:
# Use Backlog as source of truth backlog-jira sync task-123 --strategy prefer-backlog # Use Jira as source of truth backlog-jira sync task-123 --strategy prefer-jira # Resolve interactively backlog-jira sync task-123 --strategy prompt
Solution:
# Fix directory permissions
chmod 755 .backlog-jira
chmod 755 .backlog-jira/snapshots
# Rebuild if corrupted
rm -rf .backlog-jira
backlog-jira initEnable detailed logging for troubleshooting:
# Set log level
export LOG_LEVEL=debug
# Run command
backlog-jira sync task-123
# View logs
tail -f .backlog-jira/logs/backlog-jira.log-
Check documentation:
-
Run diagnostics:
backlog-jira doctor backlog-jira connect backlog-jira status
-
Enable debug logging:
LOG_LEVEL=debug backlog-jira [command]
-
Check operation logs:
backlog-jira status --history 50
# Clone repository
git clone https://github.com/YOUR-USERNAME/Backlog.md-jira-plugin.git
cd Backlog.md-jira-plugin
# Install dependencies
npm install
# Type check
npm run check:types
# Lint
npm run check
# Build
npm run build
# Run in development mode
bun run src/cli.ts --helpbacklog-jira/
├── src/
│ ├── cli.ts # CLI entry point
│ ├── commands/ # Command implementations
│ │ ├── init.ts
│ │ ├── doctor.ts
│ │ ├── connect.ts
│ │ ├── map.ts
│ │ ├── status.ts
│ │ ├── push.ts
│ │ ├── pull.ts
│ │ ├── sync.ts
│ │ ├── view.ts
│ │ └── watch.ts
│ ├── integrations/ # Backlog & Jira integrations
│ │ ├── backlog.ts
│ │ └── jira.ts
│ ├── state/ # State management
│ │ └── store.ts
│ ├── ui/ # User interface components
│ │ ├── conflict-resolver.ts
│ │ └── display-adapter.ts
│ └── utils/ # Utilities
│ ├── logger.ts
│ ├── status-mapping.ts
│ ├── sync-state.ts
│ ├── frontmatter.ts
│ └── normalizer.ts
├── docs/ # Documentation
├── backlog/ # Backlog.md tasks
├── .backlog-jira/ # Plugin state
│ ├── config.json
│ ├── snapshots/ # Snapshot JSON files
│ ├── ops-log.jsonl # Operations audit log
│ └── .gitignore
├── package.json
├── tsconfig.json
└── README.md
# Run all tests
bun test
# Run specific test
bun test src/commands/sync.test.ts
# Watch mode
bun test --watchThis plugin follows Backlog.md's plugin architecture. When contributing:
- Follow zero-coupling principle: Only use public APIs
- No core modifications: Plugin must work standalone
- External state: Store all state in
.backlog-jira/ - Test thoroughly: Add tests for new features
- Document changes: Update README and relevant docs
📚 For detailed development guidelines, see docs/DEVELOPMENT.md
Includes critical information about working with prompts, command structure, and common pitfalls.
✅ Zero Coupling: No modifications to Backlog.md core - can be installed/removed freely
✅ Bidirectional: Changes flow both ways automatically
✅ Intelligent: 3-way merge detects conflicts accurately
✅ Flexible: Project-specific status mappings and custom configurations
✅ Secure: Uses MCP protocol for safe Jira access
✅ Transparent: Dry-run mode and detailed logging
✅ Standalone: Independent lifecycle from Backlog.md updates
- Development Teams: Keep Backlog.md tasks synced with Jira for project management
- Multi-Tool Workflows: Use Backlog.md for planning, Jira for tracking
- Remote Teams: Ensure everyone has latest updates across both systems
- Compliance: Maintain audit trail with operations log
- Custom Workflows: Support complex Jira workflows with flexible mapping
- PR #394: Backlog.md Plugin System
- Backlog.md: Main Repository
- MCP Atlassian: Model Context Protocol for Atlassian
- Status Mapping Guide: docs/status-mapping.md
- AC Sync Guide: docs/acceptance-criteria-sync.md
MIT License (inherits from Backlog.md)
Copyright (c) 2025 Backlog.md Contributors
Status: Phase 5 Complete ✓ - Full sync capabilities with watch mode, performance optimization, and environment validation
For questions or issues, please check the Troubleshooting section or open an issue on GitHub.