Files
bitbucket-mcp/docs/superpowers/specs/2026-05-20-bitbucket-mcp-features-design.md
Nicolas FRADIN 666fc05dd0 Add design spec for missing Bitbucket MCP features
Covers domain client split, write operations (PR, comment, task),
and repository/workspace/branch browsing tools.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-20 18:33:35 +02:00

254 lines
9.5 KiB
Markdown

# Bitbucket MCP Server — Missing Features Design
**Date:** 2026-05-20
**Status:** Approved
---
## Overview
Extend the existing read-only Bitbucket MCP server with:
1. **Repository/workspace browsing** — list workspaces, repos, branches; get repo metadata
2. **PR write operations** — create, update, merge, decline, approve, request-changes
3. **Comment write operations** — add, update, delete comments (general and inline)
4. **Task write operations** — create, update, delete PR tasks
The server's public contract (tool names, parameter shapes, `ToolResult` response envelope) remains unchanged; new tools are additive.
---
## Architecture
The current monolithic `bitbucket-client.ts` is split into domain modules. A shared `base-client.ts` owns the Axios instance, initialization guard, error interceptors, and `formatError`. Domain clients extend or compose the base. `BitbucketClient` becomes a composition root that wires all domain clients together and exposes a flat API to the router.
```
src/
├── index.ts # MCP server — tool schemas added here
├── router.ts # Tool dispatcher — new switch cases added
├── config.ts # Unchanged
├── bitbucket-client.ts # Composition root: instantiates & delegates to domain clients
└── clients/
├── base-client.ts # Shared Axios instance, ensureInitialized, formatError, error interceptors
├── pull-request-client.ts # All PR methods (read + write)
├── repository-client.ts # Workspace, repo, branch methods
└── comment-client.ts # Comment + task CRUD
```
The router continues calling `this.client.<method>()` — the composition root makes this transparent.
---
## Domain Clients
### `base-client.ts`
Extracted from the current `bitbucket-client.ts`:
- Holds the `AxiosInstance`
- `initializeClient(options)` — loads config, creates axios, attaches interceptors
- `ensureInitialized()` — polling guard used by all domain clients
- `handleResponseError(error)` — 401/403/429 logging and rate-limit wait
- `formatError(error)` — HTTP status + message formatter
- `sleep(ms)` — utility
All domain clients receive a reference to the base client (or extend it) to share the axios instance and initialization state.
### `pull-request-client.ts`
**Existing read methods** (migrated from current `bitbucket-client.ts`):
- `listPullRequests`, `getPullRequest`, `getPullRequestActivities`
- `getPullRequestChanges`, `getPullRequestComments`, `getPullRequestComment`
- `getPullRequestCommits`, `getPullRequestDiff`, `getPullRequestPatch`
- `getPullRequestParticipants`, `getPullRequestReviewers`, `getPullRequestStatus`
- `getPullRequestTasks`, `getPullRequestTaskCount`, `getFullPullRequest`
**New write methods:**
| Method | HTTP | Endpoint |
|--------|------|----------|
| `createPullRequest(ws, repo, opts)` | POST | `/repositories/{ws}/{repo}/pullrequests` |
| `updatePullRequest(ws, repo, id, opts)` | PUT | `/repositories/{ws}/{repo}/pullrequests/{id}` |
| `mergePullRequest(ws, repo, id, opts)` | POST | `.../pullrequests/{id}/merge` |
| `declinePullRequest(ws, repo, id)` | POST | `.../pullrequests/{id}/decline` |
| `approvePullRequest(ws, repo, id)` | POST | `.../pullrequests/{id}/approve` |
| `unapprovePullRequest(ws, repo, id)` | DELETE | `.../pullrequests/{id}/approve` |
| `requestChangesPullRequest(ws, repo, id)` | POST | `.../pullrequests/{id}/request-changes` |
| `removeRequestChangesPullRequest(ws, repo, id)` | DELETE | `.../pullrequests/{id}/request-changes` |
`createPullRequest` options:
```ts
{
title: string;
source_branch: string;
destination_branch: string;
description?: string;
reviewers?: string[]; // account UUIDs or usernames
close_source_branch?: boolean;
}
```
`mergePullRequest` options:
```ts
{
merge_strategy?: 'merge_commit' | 'squash' | 'fast_forward';
commit_message?: string;
close_source_branch?: boolean;
}
```
`updatePullRequest` options (all optional, at least one required):
```ts
{
title?: string;
description?: string;
reviewers?: string[];
destination_branch?: string;
}
```
### `repository-client.ts`
New domain client — no existing methods to migrate.
| Method | HTTP | Endpoint |
|--------|------|----------|
| `listWorkspaces(opts)` | GET | `/workspaces` |
| `listRepositories(ws, opts)` | GET | `/repositories/{workspace}` |
| `getRepository(ws, repo)` | GET | `/repositories/{workspace}/{repo_slug}` |
| `listBranches(ws, repo, opts)` | GET | `/repositories/{ws}/{repo}/refs/branches` |
`listWorkspaces` options: `page`, `pagelen`
`listRepositories` options: `role` (`member`|`contributor`|`owner`), `page`, `pagelen`
`listBranches` options: `page`, `pagelen`, `filter_by_name` (maps to `q=name~"<value>"`)
### `comment-client.ts`
**Existing read methods** (migrated):
- `getPullRequestComments`, `getPullRequestComment`
**New write methods:**
| Method | HTTP | Endpoint |
|--------|------|----------|
| `addPullRequestComment(ws, repo, id, opts)` | POST | `.../pullrequests/{id}/comments` |
| `updatePullRequestComment(ws, repo, id, commentId, opts)` | PUT | `.../comments/{commentId}` |
| `deletePullRequestComment(ws, repo, id, commentId)` | DELETE | `.../comments/{commentId}` |
| `createPullRequestTask(ws, repo, id, opts)` | POST | `.../pullrequests/{id}/tasks` |
| `updatePullRequestTask(ws, repo, id, taskId, opts)` | PUT | `.../tasks/{taskId}` |
| `deletePullRequestTask(ws, repo, id, taskId)` | DELETE | `.../tasks/{taskId}` |
`addPullRequestComment` options:
```ts
{
content: string;
inline?: { path: string; to: number }; // inline comment on file:line
parent_id?: number; // reply to existing comment
}
```
`updatePullRequestComment` options:
```ts
{ content: string }
```
`createPullRequestTask` options:
```ts
{
content: string;
comment_id?: number; // anchor to a specific comment
}
```
`updatePullRequestTask` options:
```ts
{
content?: string;
state?: 'RESOLVED' | 'UNRESOLVED';
}
```
---
## New MCP Tools (index.ts)
### Repository/Workspace
| Tool name | Required params | Optional params |
|-----------|----------------|-----------------|
| `list_workspaces` | — | `page`, `pagelen` |
| `list_repositories` | `workspace` | `role`, `page`, `pagelen` |
| `get_repository` | `workspace`, `repository` | — |
| `list_branches` | `workspace`, `repository` | `filter_by_name`, `page`, `pagelen` |
### PR Write
| Tool name | Required params | Optional params |
|-----------|----------------|-----------------|
| `create_pull_request` | `workspace`, `repository`, `title`, `source_branch`, `destination_branch` | `description`, `reviewers`, `close_source_branch` |
| `update_pull_request` | `workspace`, `repository`, `pullRequestId` | `title`, `description`, `reviewers`, `destination_branch` |
| `merge_pull_request` | `workspace`, `repository`, `pullRequestId` | `merge_strategy`, `commit_message`, `close_source_branch` |
| `decline_pull_request` | `workspace`, `repository`, `pullRequestId` | — |
| `approve_pull_request` | `workspace`, `repository`, `pullRequestId` | — |
| `unapprove_pull_request` | `workspace`, `repository`, `pullRequestId` | — |
| `request_changes_pull_request` | `workspace`, `repository`, `pullRequestId` | — |
| `remove_request_changes_pull_request` | `workspace`, `repository`, `pullRequestId` | — |
### Comment Write
| Tool name | Required params | Optional params |
|-----------|----------------|-----------------|
| `add_pull_request_comment` | `workspace`, `repository`, `pullRequestId`, `content` | `inline_path`, `inline_line`, `parent_comment_id` |
| `update_pull_request_comment` | `workspace`, `repository`, `pullRequestId`, `commentId`, `content` | — |
| `delete_pull_request_comment` | `workspace`, `repository`, `pullRequestId`, `commentId` | — |
### Task Write
| Tool name | Required params | Optional params |
|-----------|----------------|-----------------|
| `create_pull_request_task` | `workspace`, `repository`, `pullRequestId`, `content` | `comment_id` |
| `update_pull_request_task` | `workspace`, `repository`, `pullRequestId`, `taskId` | `content`, `state` |
| `delete_pull_request_task` | `workspace`, `repository`, `pullRequestId`, `taskId` | — |
---
## Router (router.ts)
New `switch` cases added to `executeTool()` for every new tool. Same pattern as existing cases:
1. Extract `workspace`, `repoSlug` via `getDefaultParams()`
2. Parse integer IDs
3. Validate required params — return `{ success: false, error: '...' }` if missing
4. Delegate to `this.client.<method>()`
5. Return `{ success: true, data: result }`
No new helper is needed for write ops; the existing pattern is clear enough.
---
## Error Handling
- Write operations do not retry on failure.
- `base-client.ts` error interceptor handles 401/403/429 (existing behavior preserved).
- All domain client methods catch errors and re-throw with `this.formatError(error)`.
- Router catches and returns `{ success: false, error: '...' }`.
---
## Testing
**Unit tests (`tests/unit/router.test.ts`):**
- New cases added for every new tool, mocking `BitbucketClient` identically to existing tests.
**Integration tests (`tests/integration/bitbucket-api.test.ts`):**
- Read operations (workspaces, repos, branches) added unconditionally.
- Write operations (merge, decline, approve, comment, task) gated on `RUN_WRITE_TESTS=true` env var to prevent accidental mutation of real PRs.
---
## Out of Scope
- Webhooks, pipelines, deployments, issue tracker, snippets.
- Any Bitbucket Server (self-hosted) support — this server targets Bitbucket Cloud only.
- Streaming / SSE responses.