@cocaxcode/devflow-mcp
Connect Jira with GitHub/GitLab from your AI assistant.
32 tools · Jira Cloud + Server · GitHub + GitLab · Custom flows · Configurable rules
The Problem ·
Installation ·
Just Talk to It ·
Tools ·
Flows ·
Rules ·
Storage ·
Compatibility ·
Architecture
The Problem
You work with Jira, GitHub/GitLab, and the terminal. Every time you start a task:
- Open Jira, find the issue, read the details
- Go to the terminal, checkout main, pull, create a branch
- Back to Jira, move the issue to "In Progress"
- When done, push, create PR, back to Jira again...
devflow-mcp gives you all of this as MCP tools your AI assistant (Claude Code, Cursor, Windsurf, etc.) can use directly. Each tool works independently — the AI orchestrates, the MCP executes.
| Jira Cloud + Server | Auto-detects version (v2/v3) |
| GitHub + GitLab | Cloud and self-hosted |
| Custom flows | Editable YAML playbooks |
| Configurable rules | Global + per-project overrides |
| Multi-project | Each project with its own connection |
| Safety guards | Blocks if there are uncommitted/unpushed changes |
| Explicit confirmation | Push, merge, branch, transitions |
Installation
Claude Code (recommended)
claude mcp add devflow --scope user -- npx -y @cocaxcode/devflow-mcp
claude mcp add devflow -- npx -y @cocaxcode/devflow-mcp
Claude Desktop
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"devflow": {
"command": "npx",
"args": ["-y", "@cocaxcode/devflow-mcp"]
}
}
}
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"devflow": {
"command": "npx",
"args": ["-y", "@cocaxcode/devflow-mcp"]
}
}
}
Cursor / Windsurf
{
"mcpServers": {
"devflow": {
"command": "npx",
"args": ["-y", "@cocaxcode/devflow-mcp"]
}
}
}
VS Code / Codex / Gemini CLI
{
"mcpServers": {
"devflow": {
"command": "npx",
"args": ["-y", "@cocaxcode/devflow-mcp"]
}
}
}
Just Talk to It
You don't need to memorize tool names. Talk to your AI assistant naturally:
Start a task
"Let's work on PROJ-123"
The AI reads the issue, summarizes the task, checks if a branch already exists, creates one if not, moves the issue to "In Progress", and assigns it — all following the start-task flow.
List your issues
"Show me my tasks for the ACME project"
ACME-45 Fix OAuth login In Progress High
ACME-52 Dashboard refactor To Do Medium
ACME-61 Update dependencies To Do Low
Create a branch
"Create a fix branch for PROJ-456 with description fix-oauth-redirect"
Preview:
branch: fix/PROJ-456-fix-oauth-redirect
base: main
actions: checkout main → pull → create branch
Confirm? (confirm: true to execute)
Push with safety
"Push my changes"
Preview:
branch: feat/PROJ-123-add-login
pending commits:
- a1b2c3d feat: add login component
- d4e5f6g feat: add auth service
Confirm? (confirm: true to execute)
Merge with conflict detection
"Merge main into my current branch"
If there are conflicts, it tells you exactly which files:
Conflict detected:
- src/auth/login.ts
- src/config/routes.ts
Resolve the files and commit to complete the merge.
Create a PR
"Create a PR titled 'feat: add OAuth login'"
PR created:
url: https://github.com/org/repo/pull/42
title: feat: add OAuth login
provider: github
"Comment on PROJ-123 that the PR is ready for review"
Preview:
issue: PROJ-123
comment: "PR ready for review: https://github.com/org/repo/pull/42"
Confirm? (confirm: true to publish)
Tools
32 tools organized in 5 categories:
Projects (5 tools)
df_project_setup | Configure a new project (Jira + Git, auto-detects everything) |
df_project_update | Modify project configuration |
df_project_list | List all configured projects |
df_project_switch | Switch the active project |
df_project_delete | Delete a project |
Example: setting up a project
df_project_setup with:
name: "my-project"
jiraUrl: "https://my-company.atlassian.net"
jiraEmail: "dev@company.com"
jiraToken: "ATATT3x..."
jiraProjectKey: "PROJ"
gitToken: "ghp_..."
paths: ["C:/repos/my-project"]
Auto-detects:
- Jira Cloud (API v3)
- GitHub (org/my-project)
- Base branch: main
Jira (6 tools)
df_issues | List my assigned issues (filters by project) | — |
df_issue | Full issue detail | — |
df_statuses | Available transitions for an issue | — |
df_transition | Move issue to another status | Yes |
df_assign | Assign issue to current user | — |
df_comment | Comment on an issue | Yes |
Rules that apply to Jira
- no-close-issues (active by default): Blocks moving issues to final statuses (Done, Closed, Resolved, etc.). Only a human should close tasks from Jira directly.
- only-own-issues (active by default): Prevents transitioning, assigning, or editing issues assigned to other users. Only viewing and commenting are allowed.
Git (7 tools)
df_branch | Create branch (feat/ or fix/) from base | Yes |
df_find_branch | Search branch by issue key | — |
df_checkout | Switch branch (with guard) | — |
df_pull | Pull current branch from remote | — |
df_push | Push current branch to remote | Yes |
df_merge | Merge a branch into the current one | Yes |
df_pr | Create PR (GitHub) or MR (GitLab) | — |
[!IMPORTANT]
df_branch, df_checkout, and df_push verify the working directory state before executing. They block if there are uncommitted files or unpushed commits, listing exactly what needs to be resolved. This prevents accidental work loss.
Rules that apply to Git
- no-merge-to-base (active by default): Blocks direct push and merge to the base branch (main/master). Forces using PR/MR.
- no-merge-from-dev (active by default): Blocks merging development branches (dev, develop, int, integration, development) into other branches. These branches only receive merges, they are never merged out.
Flows (5 tools)
df_flow_create | Create a custom flow |
df_flow_list | List all flows |
df_flow_get | View flow details |
df_flow_update | Modify an existing flow |
df_flow_delete | Delete a flow (protects start-task) |
Rules (9 tools)
df_rule_create | Create a global rule | Global |
df_rule_list | List all rules | Global |
df_rule_get | View rule details | Global |
df_rule_update | Modify a rule | Global |
df_rule_toggle | Enable/disable a rule | Global |
df_rule_delete | Delete a rule | Global |
df_rule_project_override | Enable/disable a global rule for a project | Project |
df_rule_project_add | Create a project-only rule | Project |
df_rule_project_remove | Remove a project rule or override | Project |
Flows
Flows are playbooks that define step sequences. They don't run automatically — you tell the AI when to use them.
Default flow: start-task
Triggered when you say something like "let's work on PROJ-123":
name: start-task
trigger: "when the user says 'let's work on', 'start task', 'new task' + issue ID"
steps:
- tool: df_issue
note: "Read issue detail and summarize the task"
- tool: df_find_branch
note: "Check if a branch already exists for this issue"
- tool: df_branch
confirm: true
note: "Only if no existing branch was found"
- tool: df_statuses
note: "Get transitions to find the 'In Progress' transition ID"
- tool: df_transition
target: "In Progress"
confirm: true
- tool: df_assign
note: "Assign the issue if it has no assignee"
Create a custom flow
"Create a flow called 'finish-task' that pushes, creates a PR, and comments on Jira"
name: finish-task
trigger: "when the user says 'finish task', 'wrap up' + issue ID"
steps:
- tool: df_push
confirm: true
note: "Push pending commits"
- tool: df_pr
note: "Create PR/MR to base branch"
- tool: df_comment
confirm: true
note: "Comment on the issue with the PR link"
Edit a flow
"Modify the 'start-task' flow to skip the assign step"
Use df_flow_update to change steps, trigger, or name. The start-task flow can be modified but not deleted.
Rules
Rules are configurable guards that block or warn about actions. There are two levels:
Global rules
Apply to all projects. Created with df_rule_create.
Default rules (active):
no-merge-to-base | git | block | Prevent direct push/merge to main/master |
no-merge-from-dev | git | block | Prevent merging dev/int/develop branches into other branches |
no-close-issues | jira | block | Prevent closing issues (Done, Closed, Resolved...) |
only-own-issues | jira | block | Prevent modifying issues assigned to others |
Project rules
Each project can:
-
Override a global rule — enable or disable it just for that project:
"Disable the no-close-issues rule for the staging project"
Use df_rule_project_override with enabled: false.
-
Create its own rules — only apply to that project:
"Create a rule in this project that warns when pushing on Fridays"
Use df_rule_project_add.
-
Remove overrides or project rules:
"Remove the no-close-issues override in this project"
Use df_rule_project_remove.
Create a custom rule
df_rule_create:
name: "no-push-friday"
description: "Warn when pushing on Fridays"
scope: "git"
action: "warn"
Options:
- scope:
git, jira, or all
- action:
block (prevents the action) or warn (advisory only)
[!NOTE]
Rule resolution order: Global rules are loaded first, then project overrides are applied (project wins), then project-specific rules are added. Finally, rules are filtered by scope and enabled state.
Storage
All data is stored in ~/.dfm/:
~/.dfm/
├── projects/ # Project configurations (.json)
│ ├── my-project.json
│ └── other-project.json
├── flows/ # Flow definitions (.yaml)
│ └── start-task.yaml
├── rules/ # Global rules (.json)
│ ├── no-merge-to-base.json
│ ├── no-merge-from-dev.json
│ ├── no-close-issues.json
│ └── only-own-issues.json
├── active-project # Active project (plain text)
└── config.json # Server configuration
- Projects: JSON with credentials, paths, rule overrides, and project-specific rules
- Flows: Editable YAML with steps and triggers
- Rules: JSON with name, scope, action, and state
- Permissions: Project files are created with
600 permissions (owner-only)
[!TIP]
Project resolution: When you run a tool, devflow-mcp matches your current working directory against each project's paths. If no match is found, it falls back to the project set via df_project_switch. If neither works, it asks you to configure with df_project_setup.
Compatibility
Jira
| Jira Cloud | REST API v3 | Email + API Token (Basic) |
| Jira Server / Data Center | REST API v2 | Personal Access Token (Bearer) |
Auto-detection via /rest/api/2/serverInfo — you don't need to know which version you're using.
Git
| GitHub (cloud) | REST API v3 |
| GitHub Enterprise | REST API v3 (custom URL) |
| GitLab (cloud) | REST API v4 |
| GitLab self-hosted | REST API v4 (custom URL) |
Auto-detection by parsing the repository's remote URL.
Architecture
src/
├── index.ts # Entry point (stdio transport)
├── server.ts # Factory: createServer() + instructions
├── lib/
│ ├── types.ts # Interfaces, defaults, valid tool names
│ ├── storage.ts # CRUD: projects, flows, rules, config
│ ├── git-exec.ts # Git CLI wrapper (execFile)
│ ├── jira/
│ │ ├── client.ts # JiraClient (Cloud v3 + Server v2)
│ │ └── types.ts # Raw Jira API response shapes
│ └── git/
│ ├── detect.ts # parseRemoteUrl (SSH/HTTPS, GitHub/GitLab)
│ ├── github.ts # GitHubClient (REST API v3)
│ ├── gitlab.ts # GitLabClient (REST API v4)
│ ├── factory.ts # createGitProviderClient()
│ └── types.ts # Git provider interfaces
└── tools/
├── project.ts # 5 tools: setup, update, list, switch, delete
├── jira.ts # 6 tools: issues, issue, statuses, transition, assign, comment
├── git.ts # 7 tools: branch, find_branch, checkout, pull, push, merge, pr
├── flow.ts # 5 tools: create, list, get, update, delete
└── rule.ts # 9 tools: CRUD global + 3 project-level
Stack: TypeScript · MCP SDK · Zod · YAML · tsup
Tests: 4 suites · 51 tests (Vitest + InMemoryTransport)
MIT · Built by cocaxcode