keen-mcp
by imogenlabs
README.md
# Keen
<p align="center"><strong>The complete Atlassian platform CLI and MCP Server.</strong></p>
<p align="center">
<a href="https://github.com/imogenlabs/keen/releases/latest">
<img src="https://img.shields.io/github/v/release/imogenlabs/keen?label=Release&logo=github" alt="Release">
</a>
<a href="https://github.com/imogenlabs/keen/blob/main/LICENSE">
<img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="Apache 2.0 License">
</a>
<a href="https://go.dev">
<img src="https://img.shields.io/github/go-mod/go-version/imogenlabs/keen" alt="Go Version">
</a>
<a href="https://github.com/imogenlabs/keen/actions/workflows/ci.yml">
<img src="https://github.com/imogenlabs/keen/actions/workflows/ci.yml/badge.svg" alt="CI">
</a>
<a href="https://goreportcard.com/report/github.com/imogenlabs/keen">
<img src="https://goreportcard.com/badge/github.com/imogenlabs/keen" alt="Go Report Card">
</a>
</p>
<p align="center">
<a href="https://imogenlabs.ai">
<img src="https://img.shields.io/badge/Imogen_Labs-AI-8B5CF6" alt="Imogen Labs">
</a>
</p>
---
1,843 API endpoints across Jira, Confluence, Agile, Marketplace, Service Desk, Assets, Compass, Bitbucket and Admin — in a single Go binary. SQLite-backed local sync with full-text search. MCP server for AI agents. Built for automation first, humans second.
## What is Keen?
Every Atlassian workflow you automate is a wall of `curl` calls with hand-rolled JSON payloads and fragile `jq` parsing. Every AI agent that touches Jira hits rate limits after a few dozen operations.
Keen replaces all of that with typed commands, structured output, local SQLite caching with FTS5 full-text search, and an MCP server that gives AI agents fast, uncapped access to your entire Atlassian stack.
**Free and open source.** Apache-2.0 license. No account required. No telemetry.
## API Coverage
| API | Endpoints | What you get |
|-----|-----------|-------------|
| **Jira Platform v3** | 630 | Issues, transitions, search, bulk ops, fields, workflows, permissions |
| **Jira Agile** | 105 | Sprints, boards, backlogs, epics, ranking, velocity |
| **Confluence** | 130 | CQL search, spaces, labels, templates, attachments, permissions, content states |
| **Confluence v2** | 252 | Pages, blog posts, spaces, folders, databases, whiteboards, comments, attachments, space roles |
| **Marketplace v2** | 143 | Addons, versions, vendors, distribution, reviews, reporting |
| **Service Desk** | 75 | Requests, queues, customers, SLAs, approvals |
| **Assets** | 64 | Objects, object schemas and types, AQL search, imports (`keen assets-...`). keen finds your cloud and workspace ids from the site |
| **Compass** | 11 | Events, metric values, attachments and API specs for components |
| **Teamwork Graph** | 18 queries | Org chart (manager, direct reports, report chain), teams, a person's created, commented, mentioned and viewed work, over GraphQL ([Teamwork Graph](docs/Teamwork-Graph.md)). Plus `keen graphql` for any read query |
| **Bitbucket** | 300 | Repositories, pull requests, pipelines, branches, commits, snippets, workspaces (`keen bitbucket-...`). Public data works without a token; private data needs a Bitbucket-scoped token ([Authentication](docs/Authentication.md)) |
| **Admin** | 133 | Organizations, directories, groups, domains, policies, user management, user provisioning (SCIM), API tokens and service accounts, data loss prevention. Needs an admin API key ([Authentication](docs/Authentication.md)) |
## Your First 5 Minutes
```bash
# 1. Install (macOS or Linux with Homebrew; see Install for other ways)
brew install imogenlabs/tap/keen
# 2. Save your credentials
mkdir -p ~/.config/keen
cat > ~/.config/keen/auth.json <<'JSON'
{ "base_url": "https://yoursite.atlassian.net", "username": "you@example.com", "password": "your-api-token" }
JSON
chmod 600 ~/.config/keen/auth.json
# 3. Check connectivity
keen doctor
# 4. Sync your Jira data locally
keen sync
# 5. Search instantly (no API calls)
keen search "authentication bug" --data-source local
```
Generate an API token at [id.atlassian.com/manage-profile/security/api-tokens](https://id.atlassian.com/manage-profile/security/api-tokens).
Environment variables work too, and override the file:
```bash
export JIRA_CLOUD_PLATFORM_USERNAME="you@example.com"
export JIRA_CLOUD_PLATFORM_PASSWORD="your-api-token"
export JIRA_CLOUD_PLATFORM_URL="https://yoursite.atlassian.net"
```
## Install
### Homebrew (macOS and Linux)
```bash
brew install imogenlabs/tap/keen
```
This installs both `keen` and `keen-mcp`. `brew upgrade keen` picks up new releases; the formula follows each release within a day.
### Binary download
Download from [GitHub Releases](https://github.com/imogenlabs/keen/releases/latest):
```bash
# macOS (Apple Silicon)
curl -L https://github.com/imogenlabs/keen/releases/latest/download/keen_darwin_arm64.tar.gz | tar xz
sudo mv keen_darwin_arm64/keen /usr/local/bin/
sudo mv keen_darwin_arm64/keen-mcp /usr/local/bin/
# macOS (Intel)
curl -L https://github.com/imogenlabs/keen/releases/latest/download/keen_darwin_amd64.tar.gz | tar xz
sudo mv keen_darwin_amd64/keen /usr/local/bin/
sudo mv keen_darwin_amd64/keen-mcp /usr/local/bin/
# Linux (x86_64)
curl -L https://github.com/imogenlabs/keen/releases/latest/download/keen_linux_amd64.tar.gz | tar xz
sudo mv keen_linux_amd64/keen /usr/local/bin/
sudo mv keen_linux_amd64/keen-mcp /usr/local/bin/
# Linux (arm64)
curl -L https://github.com/imogenlabs/keen/releases/latest/download/keen_linux_arm64.tar.gz | tar xz
sudo mv keen_linux_arm64/keen /usr/local/bin/
sudo mv keen_linux_arm64/keen-mcp /usr/local/bin/
```
Each release also publishes `checksums.txt`.
### From source
```bash
git clone https://github.com/imogenlabs/keen.git
cd keen
make build-all # builds bin/keen and bin/keen-mcp
```
Requires Go 1.26.10 or newer.
What changed in each release: [CHANGELOG.md](CHANGELOG.md).
## MCP Server
Keen includes an MCP server (`keen-mcp`) that gives AI agents every keen command (1,939 tools). Works with Claude Code, Cursor, Windsurf, and any MCP-compatible host.
It offers two tools: `keen_search` finds the tools for a task ("transition an issue", "list sprints on a board") and returns them with their parameters, and `keen_run` runs one by name. Listing all 1,939 tools one by one would take about half a million tokens, more than most models can hold, so an app connecting to keen would fill up before doing any work. To get the full list anyway, set `KEEN_MCP_TOOLS=all` in the server's environment.
Tool calls arrive as JSON-RPC over stdio. Finding a tool happens inside keen-mcp with no call to Atlassian. Running one goes to Atlassian, except that a read repeated within 5 minutes is answered from keen's cache, so a busy agent stays under Atlassian's rate limits. Any successful write clears the cache, so a read after a write is fresh. Set `KEEN_MCP_CACHE=off` to make every read live. If a tool's API has been switched off by Atlassian, the result says what to use instead.
<p align="center">
<img src="docs/diagrams/mcp-flow.drawio.png" alt="MCP request flow: the AI app asks keen_search for a task in plain words and gets the best matching tools back without any call to Atlassian, then calls keen_run, which answers a recent repeat read from keen's cache and sends everything else to Atlassian." width="820">
</p>
Source: [`docs/diagrams/mcp-flow.drawio`](docs/diagrams/mcp-flow.drawio).
### Claude Desktop, and the MCP registry
On a Mac, download `keen-mcp.mcpb` from the [latest release](https://github.com/imogenlabs/keen/releases/latest) and open it: Claude Desktop installs keen and asks for your Atlassian site, email and API token. keen is also listed in the official [MCP registry](https://registry.modelcontextprotocol.io) as `io.github.imogenlabs/keen`, updated with every release.
`keen-mcp` finds credentials the same way the CLI does: environment variables first, then `~/.config/keen/auth.json` or `config.toml`. If you already set up the CLI, `claude mcp add --transport stdio keen -- keen-mcp` is enough. To pass them explicitly instead:
### Claude Code
```bash
claude mcp add --transport stdio keen \
--env JIRA_CLOUD_PLATFORM_USERNAME=you@example.com \
--env JIRA_CLOUD_PLATFORM_PASSWORD=your-api-token \
--env JIRA_CLOUD_PLATFORM_URL=https://yoursite.atlassian.net \
-- keen-mcp
```
### Cursor
Add to `.cursor/mcp.json`:
```json
{
"mcpServers": {
"keen": {
"command": "keen-mcp",
"env": {
"JIRA_CLOUD_PLATFORM_USERNAME": "you@example.com",
"JIRA_CLOUD_PLATFORM_PASSWORD": "your-api-token",
"JIRA_CLOUD_PLATFORM_URL": "https://yoursite.atlassian.net"
}
}
}
}
```
### Windsurf
Add to `~/.codeium/windsurf/mcp_config.json` with the same structure.
## Local Sync & Offline Search
Keen syncs your Atlassian data to a local SQLite database with FTS5 full-text search. Queries run in under 100ms against the local cache instead of hitting the Jira Cloud API.
```bash
keen sync # hydrate local cache
keen search "sprint bug" --data-source local # FTS search (sub-100ms)
keen analytics # analytics on synced data
keen stale --days 30 # find stale items
keen orphans # find orphaned items
keen load # workload distribution
```
This is especially useful for AI agents that need to make many queries without hitting [Atlassian's rate limits](https://developer.atlassian.com/cloud/jira/platform/rate-limiting/).
## Agent Mode
Every command supports `--agent` which sets JSON output, no interactive prompts, compact format, and no color in one flag. Designed for AI agent consumption.
```bash
keen agile get-all-boards --agent
keen issue transitions get PROJ-42 --agent | jq '.results.transitions[] | {id, name}'
```
The everyday Jira calls have short forms:
```bash
keen find-issues --jql "project = PROJ AND status = 'In Progress'" --agent
keen get PROJ-42 --agent
keen transition PROJ-42 --transition-name "In Progress" --agent # name resolved to its id
keen sprints 42 --state active --agent
```
## Key Flags
| Flag | What it does |
|------|-------------|
| `--agent` | JSON + no prompts + compact (use this for all scripted calls) |
| `--compact` | Only key fields (id, name, status, timestamps) |
| `--json` | Force JSON output |
| `--csv` | CSV output |
| `--dry-run` | Show what would be sent without sending |
| `--stdin` | Read request body as JSON from stdin |
| `--select "key,fields.summary"` | Keep only these fields (dotted paths reach nested ones). A mistyped field fails with a suggestion |
| `--output-summary[=N]` | If the output is over N bytes (default 4096), save it to a private temp file and print a one-line JSON summary with the file path, size and record count |
| `--quiet` | One value per line |
| `--data-source local` | Query local SQLite cache only |
## Command Discovery
```bash
keen which "sprint" # find commands by keyword
keen which "create page" # natural language search
keen api # browse all API interfaces
keen api wiki # browse Confluence endpoints
keen api agile # browse Agile endpoints
```
## Common Workflows
### Sprint Management
```bash
keen agile get-all-boards --agent
keen agile get-all-sprints BOARD_ID --state active,future --agent
keen agile create-sprint --stdin <<< '{"name":"Sprint 5","originBoardId":42}'
keen agile move-issues-to-sprint-and-rank SPRINT_ID \
--stdin <<< '{"issues": ["PROJ-42", "PROJ-43"]}'
keen agile partially-update-sprint SPRINT_ID --stdin <<< '{"state": "active"}'
```
### Release Tracking
Record git release tags as Jira versions and tag the issues they shipped:
```bash
# every release tag so far, plus an unreleased "next" for work merged since
keen release sync --repo-path . --project PROJ --backfill --head origin/main --dry-run
# after publishing a release: "next" becomes the tag, a fresh "next" is created
keen release sync --repo-path . --project PROJ --tag v1.4.0 --head origin/main
```
A version is named as the tag, released on the tag's commit date and linked to
its GitHub release. An issue gets the first release whose commits name its key
as a Fix Version; existing Fix Versions are added to, never replaced.
### Issue Lifecycle
```bash
keen jira-cloud-platform-search and-reconsile-issues-using-jql-post \
--jql "project = PROJ AND status != Done" --agent
keen issue transitions get PROJ-42 --agent
keen issue transitions do PROJ-42 --transition-id 41 --agent
keen issue comment add PROJ-42 --body "Done." # plain text is wrapped into ADF
```
### Confluence
```bash
keen wiki search-content-by-cql --cql "type=page AND space=DOCS" --agent
keen wiki create-page --space DOCS --title "Release notes" --stdin < notes.xhtml
keen wiki update-page PAGE_ID --stdin < notes.xhtml # version bumped for you
keen wiki get-page PAGE_ID --agent
keen wiki get-content-descendants PAGE_ID --agent
keen wiki add-labels-to-content PAGE_ID --stdin <<< '[{"prefix":"global","name":"adr"}]'
```
Page bodies are Confluence storage format (XHTML); Markdown is not converted.
### Marketplace
```bash
keen addons get io.example.my-addon --agent
keen addons list-search --q "story points" --agent
keen addons versions get io.example.my-addon --agent
```
## Comparison
| Feature | Keen | mcp-atlassian | Atlassian Rovo MCP | jira-cli | ACLI (Appfire) |
|---------|------|---------------|--------------------|----------|----------------|
| Endpoints | 1,843 | 98 tools | 46+ | Jira only | Many |
| Local cache | SQLite + FTS5 | No | No | No | No |
| MCP server | Yes | Yes | Yes (official) | No | No |
| Products | 9 (Jira, Confluence, Agile, Marketplace, Service Desk, Admin, Assets, Compass, Bitbucket) | 2 (Jira, Confluence) | 5 | 1 | 4 |
| Server / Data Center | No | Yes | No | Yes | Yes |
| Price | Free (Apache-2.0) | Free (open source) | Free with Cloud | Free (MIT) | $360-$6,195/yr |
| Binary | Single Go binary | Python (uvx, pip, Docker) | Remote server | Single Go binary | Requires JVM |
| Offline search | Yes | No | No | No | No |
More detail, and when to pick something else: [Comparison](docs/Comparison.md).
## Architecture
The `keen` CLI and the `keen-mcp` server share one set of commands over 1,843 REST endpoints and 18 GraphQL queries. Calls go to Atlassian, with repeat reads within 5 minutes answered from a short response cache that every write clears. `keen sync` copies data into a local SQLite + FTS5 store, which `keen search` and offline reads (`--data-source local`, or automatically when Atlassian cannot be reached) use.
<p align="center">
<img src="docs/diagrams/architecture.drawio.png" alt="Keen architecture: the keen CLI and keen-mcp server share one set of commands that call Atlassian through an HTTP client (REST and GraphQL) with a short response cache, with a local SQLite + FTS5 store filled by sync for search and offline reads. Atlassian Cloud APIs: Jira Platform, Jira Agile, Confluence v1 and v2, Marketplace, Service Desk, Admin, Assets, Compass, Bitbucket, and Teamwork Graph." width="660">
</p>
Source: [`docs/diagrams/architecture.drawio`](docs/diagrams/architecture.drawio) (editable in [draw.io](https://app.diagrams.net/)).
## Regeneration
keen is generated from 15 official Atlassian OpenAPI specs (in `specs/`) by [CLI Printing Press](https://github.com/mvanhorn/cli-printing-press), then carries its own changes on top. The list of specs lives in one place, `SPECS` in `scripts/regen/config.sh`. One script refreshes keen from Atlassian's current specs without losing those changes:
```bash
git switch -c build/refresh-specs
scripts/regen/regen.sh # download current specs, generate, merge onto keen
scripts/regen/check.sh # build, tests, lint, vulnerabilities, old-name and help checks
```
How it works: the generator's previous output (renamed to keen) is kept on GitHub as the hidden ref `refs/regen/base` (not a branch; `regen.sh` fetches it). `regen.sh` generates again from today's specs, renames that output the same way, and does a three-way git merge onto keen. Spec changes land, and keen's own edits stay. Files keen writes by hand (README, docs, changelog, the patch list) always keep keen's version. `scripts/regen/postfix.py` applies keen's standing rules to code the generator adds for the first time. `scripts/regen/placeholders.py` gives help examples a placeholder that fits each argument (the generator puts `your-token-here` in every `*Key` argument), and `check.sh` fails if one slips back.
If the merge stops on a conflict, the script lists the files. Resolve them in the work folder it prints (`scripts/regen/resolve.py ours|theirs <file>` takes one side for a whole file), then run `scripts/regen/finish.sh`. When the branch is merged to `main`, move the base forward as `regen.sh` says.
To see what Atlassian has changed since the last refresh, without changing anything, run `scripts/regen/specdiff.py`. It lists the operations added, removed or changed in each spec. A weekly GitHub workflow (`.github/workflows/spec-check.yml`) runs the same check, opens one issue when anything differs and closes it once keen is current again, so a change is never missed.
`scripts/mcp-check.py <dir with keen and keen-mcp>` tests the MCP server against a live site the way an AI app uses it: it asks `keen_search` for ten everyday tasks in plain words, runs the tool through `keen_run` and compares the answer with the CLI's. It fails when the right tool is not in the top three, or when a tool ranked above it is one Atlassian has switched off. With `--claude` it also hands Claude Code keen-mcp as its only tools and checks it answers real questions with no failed call. It picks its test data from the site, and `check.sh` runs it alongside the live smoke test.
To add an Atlassian API, add one `name=URL` line to the end of `SPECS` and run the two commands above. Nothing else needs editing.
The org chart and Teamwork Graph commands come from Atlassian's GraphQL schema instead of an OpenAPI spec. `scripts/regen/graphql.py` reads it (no login needed), keeps a snapshot in `specs/graphql.json` and writes the command table; the refresh runs it, and the weekly check compares the live schema with the snapshot.
If Atlassian retires an API, the generator stops producing its command. Add it to `internal/compat/compat.go` so the old command and MCP tool keep their name and tell people what replaced it. Every local change is listed in `.printing-press-patches.json`.
Atlassian also keeps deprecated APIs in its specs, sometimes after switching them off. `scripts/regen/deprecated.py` lists every operation the specs mark deprecated (the refresh runs it, and `check.sh` fails if the list is out of date); keen labels those commands and tools, and `keen_search` ranks them below the alternatives. When one starts answering 410 Gone, add it to `RemovedEndpoints` in `internal/compat/deprecated.go` with what to use instead: keen then hides it, its command and tool name the replacement, and `keen sync` skips it.
## Security
See [SECURITY.md](SECURITY.md) for vulnerability reporting. Keen reads API credentials from environment variables or from `~/.config/keen/auth.json`. If you use the file, keep it readable only by you (`chmod 600`). The local SQLite database contains cached API responses and should be treated as sensitive.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). Bug fixes welcome. Open an issue before submitting PRs for new features.
## License
Apache-2.0
---
<p align="center">Built by <a href="https://imogenlabs.ai">Imogen Labs</a></p>
This server cannot be deployed