{
  "markdown": "# Bitbucket MCP Server (Python)\n\n<!-- mcp-name: io.github.lawp09/bitbucket-mcp -->\n\n[![PyPI](https://img.shields.io/pypi/v/bitbucket-mcp-py)](https://pypi.org/project/bitbucket-mcp-py/)\n[![Python](https://img.shields.io/pypi/pyversions/bitbucket-mcp-py)](https://pypi.org/project/bitbucket-mcp-py/)\n[![CI](https://github.com/lawp09/bitbucket-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/lawp09/bitbucket-mcp/actions/workflows/ci.yml)\n[![CodeQL](https://github.com/lawp09/bitbucket-mcp/actions/workflows/codeql.yml/badge.svg)](https://github.com/lawp09/bitbucket-mcp/actions/workflows/codeql.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nConnect **Claude Code**, **OpenAI Codex**, **Cursor**, **VS Code (GitHub Copilot)**, and any MCP-compatible AI assistant to your Bitbucket Cloud repositories. Review pull requests, monitor pipelines, and manage your code — all through natural language.\n\n## Features\n\n- **60+ MCP tools** — repositories, pull requests, comments, tasks, diffs, pipelines (runtime + config), build statuses, reviewers, draft PRs, batch review, issue tracker, commits, source/file browsing\n- **MCP 2025 tool annotations** — every tool advertises `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint` + a human-readable title, so clients (Claude Code, Cursor) auto-include read-only tools and warn before destructive operations\n- **Slim responses** — stripped API noise for lower LLM token usage\n- **Configurable** — enable/disable tools via `configs/tools.json` or `BITBUCKET_TOOLS_CONFIG` env var\n- **Secure credentials** — environment variables or system keychain\n\n## Quick Start\n\n### 1. Install\n\nThe recommended way to run the server is via **uvx** (zero install, isolated environment):\n\n```bash\n# Always latest version\nuvx --from bitbucket-mcp-py bitbucket-mcp\n\n# Pin a specific version\nuvx --from bitbucket-mcp-py==1.8.1 bitbucket-mcp\n```\n\n> **Why `--from`?** The PyPI package is `bitbucket-mcp-py` but the command entry point is `bitbucket-mcp`. The `--from` flag tells uvx which package to install.\n\n<details>\n<summary>Alternative install methods</summary>\n\n| Mode | Command | Best for |\n|------|---------|----------|\n| **pip global** | `pip install bitbucket-mcp-py` | Simple, persistent install |\n| **Local dev** | `pip install -e .` in project dir | Contributing to the project |\n| **Docker** | See [Docker section](#docker-alternative) | Container-based workflows |\n\n</details>\n\n### 2. Configure credentials\n\nSet the following environment variables (or use a `.env` file — see [Credentials](#credentials)):\n\n| Variable | Description |\n|----------|-------------|\n| `BITBUCKET_USERNAME` | Your Bitbucket email |\n| `BITBUCKET_TOKEN` | Your Bitbucket API token |\n| `BITBUCKET_WORKSPACE` | Your workspace slug |\n\n> **Get your API token** at: https://id.atlassian.com/manage-profile/security/api-tokens\n>\n> ⚠️ **Use a scoped token, not a global one.** When creating the token, select specific scopes (e.g. `Repositories: Read`, `Pull requests: Read/Write`). Global tokens without explicit scopes do not work with this MCP server.\n\n### 3. Configure your AI assistant\n\n#### Claude Code (recommended)\n\n**Option A — CLI (fastest):**\n\n```bash\nclaude mcp add bitbucket-mcp \\\n  -e BITBUCKET_USERNAME=your-email@example.com \\\n  -e BITBUCKET_TOKEN=your-api-token \\\n  -e BITBUCKET_WORKSPACE=your-workspace \\\n  -- uvx --from bitbucket-mcp-py bitbucket-mcp\n```\n\n**Option B — JSON config** (`~/.claude.json` or project `.mcp.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"bitbucket-mcp\": {\n      \"type\": \"stdio\",\n      \"command\": \"uvx\",\n      \"args\": [\"--from\", \"bitbucket-mcp-py\", \"bitbucket-mcp\"],\n      \"env\": {\n        \"BITBUCKET_USERNAME\": \"your-email@example.com\",\n        \"BITBUCKET_TOKEN\": \"your-api-token\",\n        \"BITBUCKET_WORKSPACE\": \"your-workspace\"\n      }\n    }\n  }\n}\n```\n\n#### OpenAI Codex\n\n**Option A — CLI (fastest):**\n\n```bash\ncodex mcp add bitbucket-mcp \\\n  --env BITBUCKET_USERNAME=your-email@example.com \\\n  --env BITBUCKET_TOKEN=your-api-token \\\n  --env BITBUCKET_WORKSPACE=your-workspace \\\n  -- uvx --from bitbucket-mcp-py bitbucket-mcp\n```\n\n**Option B — TOML config** (`~/.codex/config.toml`):\n\n```toml\n[mcp_servers.bitbucket-mcp]\ncommand = \"uvx\"\nargs = [\"--from\", \"bitbucket-mcp-py\", \"bitbucket-mcp\"]\nenv = { BITBUCKET_USERNAME = \"your-email@example.com\", BITBUCKET_TOKEN = \"your-api-token\", BITBUCKET_WORKSPACE = \"your-workspace\" }\n```\n\n#### Cursor\n\nAdd to `~/.cursor/mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"bitbucket-mcp\": {\n      \"command\": \"uvx\",\n      \"args\": [\"--from\", \"bitbucket-mcp-py\", \"bitbucket-mcp\"],\n      \"env\": {\n        \"BITBUCKET_USERNAME\": \"your-email@example.com\",\n        \"BITBUCKET_TOKEN\": \"your-api-token\",\n        \"BITBUCKET_WORKSPACE\": \"your-workspace\"\n      }\n    }\n  }\n}\n```\n\n#### VS Code (GitHub Copilot)\n\nAdd to `.vscode/mcp.json` (workspace) or `~/Library/Application Support/Code/User/mcp.json` (global, macOS):\n\n```json\n{\n  \"servers\": {\n    \"bitbucket-mcp\": {\n      \"type\": \"stdio\",\n      \"command\": \"uvx\",\n      \"args\": [\"--from\", \"bitbucket-mcp-py\", \"bitbucket-mcp\"],\n      \"env\": {\n        \"BITBUCKET_USERNAME\": \"your-email@example.com\",\n        \"BITBUCKET_TOKEN\": \"your-api-token\",\n        \"BITBUCKET_WORKSPACE\": \"your-workspace\"\n      }\n    }\n  }\n}\n```\n\n## Available Tools\n\n| Category | Tools |\n|----------|-------|\n| **Repositories** | `list_repositories`, `get_repository`, `get_repository_tags` |\n| **Pull Requests** | `get_pull_requests`, `get_pull_request`, `create_pull_request`, `update_pull_request`, `approve_pull_request`, `unapprove_pull_request`, `request_changes_pull_request`, `unrequest_changes_pull_request`, `decline_pull_request`, `merge_pull_request` |\n| **Comments** | `get_pull_request_comments`, `add_pull_request_comment`, `get_pull_request_comment`, `update_pull_request_comment`, `delete_pull_request_comment`, `resolve_pull_request_comment`, `reopen_pull_request_comment`, `get_pull_request_activity` |\n| **Tasks PR** | `get_pull_request_tasks`, `get_pull_request_task`, `create_pull_request_task`, `update_pull_request_task`, `delete_pull_request_task` |\n| **Diff / Review** | `get_pull_request_diff`, `get_pull_request_patch`, `get_pull_request_diffstat`, `get_pull_request_commits` |\n| **PR Discovery** | `get_pull_requests_pending_review` |\n| **Build / CI** | `get_pull_request_statuses`, `get_commit_statuses` |\n| **Pipelines** | `list_pipeline_runs`, `get_pipeline_run`, `get_pipeline_steps`, `get_pipeline_step_logs`, `run_pipeline`, `stop_pipeline` |\n| **Pipelines Config** | `get_pipeline_config`, `list_pipeline_variables`, `get_pipeline_variable`, `create_pipeline_variable`, `update_pipeline_variable`, `delete_pipeline_variable`, `list_pipeline_schedules`, `get_pipeline_schedule`, `list_pipeline_schedule_executions`, `create_pipeline_schedule`, `update_pipeline_schedule`, `delete_pipeline_schedule`, `list_pipeline_caches`, `delete_pipeline_cache` |\n| **Reviewers** | `get_effective_default_reviewers`, `suggest_pull_request_reviewers` |\n| **Draft PR** | `create_draft_pull_request`, `publish_draft_pull_request`, `convert_pull_request_to_draft` |\n| **Batch Review** | `submit_pull_request_batch_review` |\n| **Review Summary** | `get_pull_request_review_summary` |\n| **Issues** | `list_issues`, `get_issue`, `create_issue`, `update_issue`, `delete_issue`, `get_issue_comments`, `get_issue_comment`, `add_issue_comment`, `update_issue_comment`, `delete_issue_comment` |\n| **Commits** | `list_commits`, `get_commit`, `get_commit_comments`, `get_commit_comment`, `add_commit_comment` |\n| **Source** | `get_file_content`, `list_directory` |\n| **Deployments** | `list_environments`, `get_environment`, `create_environment`, `delete_environment`, `list_deployments`, `get_deployment`, `list_deployment_variables`, `create_deployment_variable`, `update_deployment_variable`, `delete_deployment_variable` |\n| **Branch Restrictions** | `list_branch_restrictions`, `get_branch_restriction`, `create_branch_restriction`, `update_branch_restriction`, `delete_branch_restriction` |\n| **Workspace** | `list_workspace_members`, `get_workspace_member`, `list_workspace_permissions`, `list_repository_permissions` |\n\n> Disabled by default: `merge_pull_request` (safety), `stop_pipeline` (safety), `get_pull_request_patch` (git am format — not useful for AI review), `convert_pull_request_to_draft` (not supported by Bitbucket API), `delete_issue` (safety), `delete_issue_comment` (safety), `add_commit_comment` (write op), `create_pipeline_variable` / `update_pipeline_variable` / `delete_pipeline_variable` (write ops), `create_pipeline_schedule` / `update_pipeline_schedule` / `delete_pipeline_schedule` (write ops), `delete_pipeline_cache` (safety), `create_environment` / `delete_environment` / `create_deployment_variable` / `update_deployment_variable` / `delete_deployment_variable` (write ops), `create_branch_restriction` / `update_branch_restriction` / `delete_branch_restriction` (write ops). Enable in `configs/tools.json`.\n\n> **Governance scopes** — Branch restriction read tools need the `repository` scope (`repository:admin` may be required depending on repo config); the write tools need `repository:admin`. Workspace member/permission tools need the `account` scope. The `/members` endpoint lists users **without** a per-user permission (use `list_workspace_permissions` for roles).\n\n> **Deployments scopes** — the read tools (`list_environments`, `get_environment`, `list_deployments`, `get_deployment`, `list_deployment_variables`) need the `deployment` scope; the write tools need `deployment:write`. Bitbucket has no server-side filter for deployments by environment ([BCLOUD-18729](https://jira.atlassian.com/browse/BCLOUD-18729)) — filter on the `environment` field of `list_deployments` instead. There is no `update_environment` tool: Bitbucket exposes no `PUT` for environments (only `POST .../changes` for locking).\n\n### Custom tool configuration\n\nBy default the server reads `configs/tools.json` bundled with the package. You can point to a custom file at runtime without rebuilding:\n\n```bash\nexport BITBUCKET_TOOLS_CONFIG=/path/to/my-tools.json\n```\n\n**Fallback chain** (first match wins):\n\n1. `BITBUCKET_TOOLS_CONFIG` environment variable\n2. Built-in `configs/tools.json`\n\n> **Fail-safe behaviour** — If `BITBUCKET_TOOLS_CONFIG` is set but the file is missing or contains invalid JSON, the server raises an error on startup (explicit failure rather than silently ignoring the override). If the built-in default is missing, all tools are enabled.\n\n> **Token tip** — `get_pull_request_diff` accepts an optional `path` parameter to filter the diff to a single file, reducing token usage by ~95% on large PRs:\n> ```\n> get_pull_request_diff(repo_slug, pull_request_id, path=\"src/services/myService.ts\")\n> ```\n>\n> **Token tip** — `get_pipeline_step_logs` returns only the trailing 100 KiB of a step log by\n> default (raw logs run to several MB on long steps). The response carries a `truncated` flag;\n> widen the window with the absolute byte range `start` / `end`, or pass `max_bytes=null` for\n> the whole log. Pass a service container UUID as `log_uuid` to read that service's log\n> instead of the build container's. This endpoint needs a real pipeline **UUID** — resolve it\n> via `get_pipeline_run` if you only have a build number.\n> ```\n> get_pipeline_step_logs(repo_slug, pipeline_uuid=\"{adab6a1f-...}\", step_uuid=\"{84fc6465-...}\")\n> ```\n\n## MCP Prompts\n\nThe server also exposes **MCP Prompts** — parameterised templates that compatible clients (Claude Code, Cursor, ...) surface as slash commands. Instead of remembering tool names, you invoke a prompt and the assistant orchestrates the right tools for you. They appear in the client's prompt picker (`prompts/list`).\n\n| Prompt | Arguments | What it does |\n|--------|-----------|--------------|\n| `review_pull_request` | `repo_slug`, `pull_request_id` | Full AI review: metadata → diffstat → diff → comments → tasks, then Summary / Risk / Quality / Security / Recommendation |\n| `debug_pipeline_failure` | `repo_slug`, `pipeline_uuid` | Diagnose a failed pipeline: run → steps → failed-step logs, then Root cause / Failed step / Error / Fix |\n| `summarize_repository` | `repo_slug` | Repo overview: info → recent commits → open PRs → CI → issues, then Purpose / Activity / Health / Contributors |\n| `onboard_reviewer` | `repo_slug`, `pull_request_id` | Help a new reviewer: PR context → commits → diff → review history, then Context / Changes / Review-so-far / Focus |\n\nPrompts are enabled/disabled in `configs/tools.json` under the top-level `prompts` key (separate from `tools`).\n\n## Credentials\n\n### Option 1: `.env` file (recommended)\n\n```bash\ncp .env.example .env\n# Edit .env with your credentials\n```\n\n### Option 2: System keychain (most secure)\n\n```bash\npip install 'bitbucket-mcp-py[keyring]'\npython3 -c \"import keyring; keyring.set_password('bitbucket-mcp', 'bitbucket_token', 'YOUR_TOKEN')\"\n```\n\n## Docker (Alternative)\n\nIf you prefer running the server in a container:\n\n```bash\ndocker build -t bitbucket-mcp-py .\ndocker run -d --name bitbucket-mcp --env-file .env bitbucket-mcp-py\n```\n\nThen configure your AI assistant to use `docker exec`:\n\n```json\n{\n  \"mcpServers\": {\n    \"bitbucket-mcp\": {\n      \"command\": \"docker\",\n      \"args\": [\"exec\", \"-i\", \"bitbucket-mcp\", \"python\", \"-m\", \"src.main\", \"--transport\", \"stdio\"]\n    }\n  }\n}\n```\n\n### Transports\n\nThe server speaks **stdio** by default (the standard transport for local MCP clients). For a network deployment it also supports **Streamable HTTP** (MCP spec 2025-03-26):\n\n```bash\n# Streamable HTTP on 0.0.0.0:8080\npython -m src.main --transport http --host 0.0.0.0 --port 8080\n```\n\n> Clients connect to `http://<host>:<port>/mcp` (e.g. `http://localhost:8080/mcp`).\n\n> `--transport sse` (legacy Server-Sent Events) is still accepted but **deprecated** — it emits a `DeprecationWarning`. Prefer `--transport http`.\n\n#### Stateless HTTP (horizontal scaling / serverless)\n\n`--stateless` runs the Streamable HTTP transport without server-side sessions: no `Mcp-Session-Id`, a fresh transport per HTTP request. Any instance behind a load balancer can serve any request — **no sticky sessions required**.\n\n```bash\npython -m src.main --transport http --host 0.0.0.0 --port 8080 --stateless\n```\n\n> ⚠️ **Single-tenant by default.** Without `--multi-tenant` the server serves *its own* process-wide Bitbucket token to every caller. Deploy it on a private network or behind an authenticated reverse proxy — or use [multi-tenant mode](#multi-tenant-http-per-request-credentials), where each caller brings their own credentials.\n\n`--stateless` requires `--transport http` (it is rejected on `stdio` and on the legacy `sse`, whose app ignores the setting). It also forces a **single JSON response** instead of an SSE stream, because edge/serverless runtimes cannot hold a streaming response open — there is currently no way to combine stateless with streaming.\n\nA liveness endpoint is exposed on both HTTP transports for load balancers:\n\n```bash\ncurl http://localhost:8080/healthz    # {\"status\": \"ok\"}\n```\n\n**In a container** — the image's default `CMD` keeps it idle for `exec`-based stdio usage, so server mode is started by overriding the command:\n\n```bash\npodman run -d --name bitbucket-mcp-http -p 8000:8000 --env-file .env bitbucket-mcp-py \\\n  python -m src.main --transport http --host 0.0.0.0 --port 8000 --stateless\n```\n\n> Works identically with `docker run`. The image exposes port 8000.\n\n| Environment variable | Default | Purpose |\n|---|---|---|\n| `BITBUCKET_ALLOWED_HOSTS` | *(unset)* | Comma-separated `Host` allowlist. Enables DNS-rebinding protection when set. |\n| `BITBUCKET_ALLOWED_ORIGINS` | *(unset)* | Comma-separated `Origin` allowlist. |\n| `BITBUCKET_MAX_PAGES_HARD_CAP` | `10` | Max pages a single tool call may fetch **in stateless mode**. Beyond it the response carries `truncated: true` — never a silent cut. |\n\n> The two allowlists must be **set together**: an empty `Host` allowlist rejects every request (`421`), and an empty `Origin` allowlist rejects every browser client (`403`). Setting only one is refused at startup rather than silently locking the server out.\n\n```bash\nexport BITBUCKET_ALLOWED_HOSTS=\"mcp.example.com\"\nexport BITBUCKET_ALLOWED_ORIGINS=\"https://app.example.com\"\n```\n\n> With neither allowlist set, no DNS-rebinding protection is applied — appropriate for a server reached through a private network or a trusted proxy. Set them as soon as the server is exposed on a real hostname.\n\n#### Multi-tenant HTTP (per-request credentials)\n\nBy default an HTTP deployment is **single-tenant**: every caller acts with the process-wide Bitbucket token. `--multi-tenant` changes that — each request carries the **caller's own Bitbucket OAuth access token** as `Authorization: Bearer`, and runs under that identity. The server holds no Bitbucket credential of its own.\n\n```bash\nBITBUCKET_RESOURCE_SERVER_URL=https://mcp.example.com \\\n  python -m src.main --transport http --host 0.0.0.0 --port 8080 --stateless --multi-tenant\n```\n\nThe token is verified against `GET /2.0/user`, which yields the caller's `account_id` and default workspace; the same token is then reused for the downstream API calls, so no credential is ever stored or mapped. Unauthenticated requests get a `401` with a `WWW-Authenticate` challenge pointing at `/.well-known/oauth-protected-resource`.\n\nWhat this buys you:\n\n- **Isolation** — one Bitbucket client per `(identity, workspace)`; two callers never share one, and there is no process token to fall back on.\n- **`workspace=None` means *your* workspace** — resolved from the caller's memberships, never from `BITBUCKET_WORKSPACE`. With zero or several memberships there is no default and calls must name their workspace.\n- **Audit trail** — every call is logged to the `bitbucket_mcp.audit` logger with the tool, the `account_id` and the workspace. Never credentials.\n- **Tighter defaults** — tools flagged `destructiveHint` are refused unless explicitly enabled.\n\n| Environment variable | Default | Purpose |\n|---|---|---|\n| `BITBUCKET_RESOURCE_SERVER_URL` | *(required)* | This server's public URL — the OAuth resource identifier |\n| `BITBUCKET_OAUTH_ISSUER_URL` | `https://bitbucket.org` | Advertised authorization server |\n| `BITBUCKET_CLIENT_CACHE_SIZE` / `_TTL` | `128` / `900` | Bound on the per-identity client cache (LRU + TTL, seconds). TTL `0` builds a fresh client per request |\n| `BITBUCKET_TOKEN_CACHE_SIZE` / `_TTL` | `256` / `300` | Bound on cached token verifications. The TTL is the **revocation window** — set it to `0` to verify every request |\n| `BITBUCKET_MULTITENANT_ALLOW_DESTRUCTIVE` | *(off)* | Allow `merge`, `decline`, `delete_*`, `stop_pipeline` |\n| `BITBUCKET_MULTITENANT_READ_ONLY` | *(off)* | Expose read-only tools only |\n\n> **Not supported in this mode**: Bitbucket Repository/Workspace Access Tokens — they are not bound to a user account, so no identity can be derived. Use single-tenant HTTP for that. Bearer tokens require TLS: terminate HTTPS in front of the server.\n\n**stdio is unaffected** — it stays single-user with environment variables, exactly as documented above.\n\nSee **[docs/deployment-modes.md](docs/deployment-modes.md)** for the full matrix of the three deployment modes and the threat model of each.\n\n## Development\n\n```bash\n# Install dev dependencies\nuv sync --extra dev\n\n# Run tests\nuv run pytest tests/ -v\n\n# Run specific test\nuv run pytest tests/test_client.py -v\n```\n\n## Requirements\n\n- Python 3.12+\n- Bitbucket API token\n\n## License\n\nMIT\n\n## References\n\n- [MCP Registry](https://registry.modelcontextprotocol.io/) — Official MCP server registry\n- [PyPI Package](https://pypi.org/project/bitbucket-mcp-py/) — Python package\n- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)\n- [Bitbucket API 2.0](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/)\n- [FastMCP Framework](https://gofastmcp.com/)\n",
  "bytes": 19989,
  "sha": "f2bdb3c8ef8395a40d9c871deaf11a0a3efddbe68ed1a7677b607a33c3f6df3b",
  "repo_slug": "lawp09/bitbucket-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_lawp09_bitbucket_mcp_68c310b6/readme"
}