mcp-bitbucket
README.md
# mcp-bitbucket
Bitbucket Server for AI agents and the humans next to them. Read pull requests and their diffs without a clone, draft a review and publish it with a verdict, post blocking tasks, browse code and commits, open, merge or decline pull requests — from an MCP client, from a shell, or from your own TypeScript.
Bitbucket **Server / Data Center** only. Bitbucket Cloud is not supported.
## Packages
| Package | What it is | Bin |
|---|---|---|
| [`core`](packages/core) | Bitbucket client, operations, formatters — everything else is a thin adapter over it | — |
| [`mcp`](packages/mcp) | MCP server, 29 tools over stdio or Streamable HTTP | `mcp-bitbucket` |
| [`cli`](packages/cli) | Shell client for the same operations, infers the repo from your git remote | `bb` |
| [`skill`](packages/skill) | Claude Code skill that teaches an agent the `bb` CLI | — |
Claude Code / Claude Desktop tool use → **mcp**. Terminal, scripts, CI, agents that run commands → **cli**. Building something else → **core**. They read the same credentials and install side by side.
## Prerequisites
- Node.js 18+, pnpm 10+ (`corepack enable`)
- A Bitbucket Server Personal Access Token — profile → Manage Account → HTTP Access Tokens, with repository read + pull request write
- Windows, macOS and Linux. Only Linux needs a package for `bb login` (OS keyring): `sudo apt install -y libsecret-1-0 gnome-keyring`. Every command also works from `BITBUCKET_TOKEN`.
## Install
```bash
pnpm install
pnpm build # core first, then mcp and cli
```
Entry points: `packages/mcp/dist/index.js`, `packages/cli/dist/index.js`.
## Quick start
### MCP server
```bash
claude mcp add mcp-bitbucket \
-e BITBUCKET_URL=https://bitbucket.example.com \
-e BITBUCKET_TOKEN=your-personal-access-token \
-- node /absolute/path/to/mcp-bitbucket/packages/mcp/dist/index.js
```
Or in `.mcp.json` (`${VAR}` is expanded by Claude Code, so no secret is committed):
```json
{
"mcpServers": {
"mcp-bitbucket": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/mcp-bitbucket/packages/mcp/dist/index.js"],
"env": {
"BITBUCKET_URL": "${BITBUCKET_URL}",
"BITBUCKET_TOKEN": "${BITBUCKET_TOKEN}"
}
}
}
}
```
HTTP transport instead of stdio: `node packages/mcp/dist/index.js --http` (port 3000) or `MCP_HTTP_PORT=8080 node …`. Routes: `POST /mcp`, `GET /mcp` (SSE), `DELETE /mcp`.
### bb CLI
```bash
pnpm --filter @cuonghuunguyen/cli link --global
bb login # or export BITBUCKET_URL / BITBUCKET_TOKEN
bb whoami # proves authentication — `bb ping` only proves reachability
bb pr get 42 # repository comes from the git origin remote
```
Command surface: [`packages/cli/README.md`](packages/cli/README.md) or `bb --help`.
### Claude Code skill
```bash
npx skills add . -a claude-code -g # global: ~/.claude/skills
npx skills add . -a claude-code # project scope: ./.claude/skills
```
Run from `packages/skill`. [`skills`](https://github.com/vercel-labs/skills) works on
Windows, macOS and Linux, and installs into any of its 75+ supported agents.
Needs `bb` on `PATH` and a resolvable credential.
## Configuration
Environment variables. A `.env` in the **working directory** is loaded automatically (`cp .env.example .env`) — an MCP server launched with a different cwd will not see it, so prefer real environment variables there. The MCP server validates them at startup; the CLI also accepts `--url` / `--token`, which win over everything, and falls back to the OS keyring when neither the flags nor the variables are set.
| Variable | Default | Effect |
|---|---|---|
| `BITBUCKET_URL` | — | Instance base URL. Required for the MCP server. |
| `BITBUCKET_TOKEN` | — | Personal Access Token. Required for the MCP server. |
| `MCP_PERMISSION_MODE` | `full` | Which verbs the MCP server exposes — see [Permissions](#permissions). |
| `BITBUCKET_ALLOWED_PROJECTS` / `BITBUCKET_ALLOWED_REPOS` | unset (all) | Repository allowlist — see [Permissions](#permissions). |
| `MCP_HTTP_PORT` | unset | Enables HTTP transport on this port instead of stdio. |
| `LOG_LEVEL` | `info` | `fatal` … `trace`. Logs go to stderr. |
| `BITBUCKET_API_BASE` | `api/1.0` | REST module and version. |
| `BITBUCKET_USER_SLUG` | resolved | User slug the review tools act as, when a proxy strips `X-AUSERNAME`. |
| `BITBUCKET_REPO` | unset | CLI only: default for `--repo`. |
### Limits
The caps in `packages/core/src/operations/caps.ts` are env-overridable: `BITBUCKET_DIFF_MAX_CHARS` (60000), `BITBUCKET_DIFF_HEAD_RATIO` (0.6), `BITBUCKET_DIFF_CONTEXT_LINES` (3), `BITBUCKET_DIFF_FETCH_MAX_CHARS` (12000000), `BITBUCKET_DIFF_CACHE_ENTRIES` (4), `BITBUCKET_CHANGED_FILES_MAX_ITEMS` (1000), `BITBUCKET_CHANGED_FILES_STATS_MAX_FILES` (500), `BITBUCKET_REQUEST_TIMEOUT_MS` (30000). Two limits are fixed in code and not overridable: the 500 KB single-file ceiling (`operations/repository.ts`) and the code-search bounds (`operations/search.ts`). Truncation is never silent — the note names the parameter and the variable that lift the cap, and lists every elided hunk header.
## Permissions
Two independent guards.
**`MCP_PERMISSION_MODE` — MCP server only.** Every tool is classified `read`, `write` or `destructive` in `packages/mcp/src/permissions.ts`; a forbidden tool is both hidden from `ListTools` and refused when called by name.
| Mode | Tools |
|---|---|
| unset / `full` | 29 |
| `modify` | 26 — no `merge_pull_request`, `decline_pull_request`, `delete_pr_comment` |
| `readonly` | 17 |
| anything else | **0** — a typo fails closed, with a warning on stderr |
The CLI does not read it. A `readonly` deployment does not stop `bb review merge`.
**Repository allowlist — both entry points.** `BITBUCKET_ALLOWED_PROJECTS` / `BITBUCKET_ALLOWED_REPOS` take comma-, semicolon- or space-separated patterns: `PROJ`, `PROJ/*`, `PROJ/repo-a`. The check runs inside the shared client before any socket opens, so every tool and every `bb` command inherits it. Unset means every repository. Case-insensitive; personal repos are project `~username`. A malformed pattern is dropped, never widened — a value made only of malformed patterns allows nothing.
## Tools
29 MCP tools: 17 read, 9 write, 3 destructive. Full parameters in [`docs/tools.md`](docs/tools.md).
| Group | Tools |
|---|---|
| Diagnostic | `ping_bitbucket` |
| Repository | `list_projects` · `list_repositories` · `list_branches` · `get_file_content` |
| Pull request | `list_pull_requests` · `get_pull_request` · `get_pull_request_comments` · `create_pull_request` · `update_pull_request` |
| Comment | `add_pr_comment` · `create_pr_task` · `update_pr_comment` |
| Context | `get_current_repo` · `find_pull_request` |
| Diff | `list_pr_changed_files` · `get_pull_request_diff` |
| Commit | `list_commits` · `get_commit_detail` |
| Browse | `browse_directory` |
| Search | `search_code` |
| Review | `add_pr_draft_comment` · `get_pr_draft_review` · `submit_pr_review` · `discard_pr_draft_review` · `set_review_status` · `merge_pull_request` · `decline_pull_request` · `delete_pr_comment` |
## Notable defaults
- **Resolved discussion is hidden.** `get_pull_request_comments` / `bb comment ls` need `includeResolved` (`--include-resolved`) to show resolved threads and tasks; the reply says how many it hid. An explicit `state` filter overrides the default.
- **Reviews are drafted, not posted.** `add_pr_draft_comment` / `bb review draft` store an invisible comment; `submit_pr_review` publishes every pending comment with a verdict as one notification. `pending: false` (`--no-pending`) posts immediately; `discard_pr_draft_review` throws the draft away.
- **`bb ping` ≠ authentication.** `/application-properties` answers 200 anonymously on many instances. `bb whoami` is the authentication check.
## Development
```bash
pnpm build | typecheck | test | lint | format | clean
pnpm dev:mcp # MCP server from source (tsx)
pnpm cli pr ls # bb from source
pnpm --filter @cuonghuunguyen/core test
```
The layering rule that keeps the adapters from drifting:
- **core knows nothing about MCP or the CLI** — no MCP SDK, no yargs, no stdout. Operations take a client plus params and return structured data or throw.
- **Adapters own input validation and output shape** — zod plus the MCP result shape in `mcp`, yargs plus text/`--json` in `cli`. Neither leaks into core.
- **Error messages are written once, in core**, with `{projects}` / `{login}` style placeholders each adapter renders in its own vocabulary — the same failure says "use `list_projects`" to a model and "use `bb project ls`" to you.
- **A new capability** is an operation plus a formatter in core, then a thin adapter in each of `mcp` and `cli`.
## Docker
The image carries the MCP server only.
```bash
docker build -t mcp-bitbucket .
docker run -i --rm -e BITBUCKET_URL=… -e BITBUCKET_TOKEN=… mcp-bitbucket
docker run --rm -p 3000:3000 -e BITBUCKET_URL=… -e BITBUCKET_TOKEN=… -e MCP_HTTP_PORT=3000 mcp-bitbucket
```
## Troubleshooting
[`docs/troubleshooting.md`](docs/troubleshooting.md). The three most common: a tool missing from the client list means `MCP_PERMISSION_MODE` hid it (an unrecognised value hides all 29); `Refused: … outside the configured repository allowlist` means the allowlist does not cover that repo; fewer comments than the web UI means resolved threads are hidden.
## Limitations
- Bitbucket Server only; no Bitbucket Cloud, no OAuth/SSO/app passwords.
- `bb login` stores credentials in the OS keyring only — no plaintext store, no `--password`.
- No webhooks or event-driven mode; the server answers tool calls.
- Diffs are server-rendered unified diff text; inline comments anchored into the diff are not exposed.
- `search_code` needs the instance code index: default branch, whole words, no regex.
- `MCP_PERMISSION_MODE` gates the MCP server only; the allowlist gates both.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues