Skip to main content

Unified Tool Server

This directory contains an alternative Bitbucket MCP server implementation that exposes all functionality through a single unified tool backed by comprehensive server instructions, following the patterns outlined in the MCP Server Instructions blog post.

Overview

Instead of providing 10 separate tools (pr_list, pr_overview, pr_review, etc.), the unified server exposes a single bitbucket tool with an action parameter. The server instructions (~200 lines) provide:

  • Recommended workflows for common tasks (PR review, creation, pipeline debugging, workspace discovery)
  • Action-specific guidance with parameter requirements and usage patterns
  • Performance optimization tips (batching, pagination, auto-reviewers)
  • Rate limits and constraints to prevent API abuse
  • Error handling guidance for common issues
  • Security notes for credential management

Architecture

unified_server.py                    # Main entry point
├─ src/modules/tools/unified.py     # Single tool implementation
└─ Uses same infrastructure as server.py:
   ├─ src/modules/resources/         # MCP resources (unchanged)
   ├─ src/modules/prompts/           # MCP prompts (unchanged)
   ├─ src/modules/middleware/        # Auth middleware (unchanged)
   └─ src/utils/                     # Shared utilities (unchanged)

Running the Unified Server

# Direct execution
python unified_server.py --host 0.0.0.0 --port 8000

# With environment variables
FASTMCP_HOST=localhost FASTMCP_PORT=8000 python unified_server.py

Tool Interface

Single Tool: bitbucket

All Bitbucket operations are performed through this one tool by specifying the action parameter.

Available Actions:

  • pr_list - List pull requests with filtering
  • pr_overview - Get PR digest with blockers, comments, diffstat
  • pr_review - Generate structured review summary
  • pr_comment_add - Add PR comments (inline or general, with verified @mentions)
  • pr_comments_list - List comments on a pull request
  • pr_tasks_list - List PR tasks with details
  • pr_tasks_sync - Create/resolve PR tasks in batch
  • pr_upsert - Create or update pull requests
  • pipe_fail_summary - Analyze failing pipeline steps
  • workspace_list - List repos, members, projects, or reviewers
  • repo_get - Get repository basics with PR sample
  • me_whoami - Get authenticated user identity

Example Usage

# List open PRs for current user
{
  "action": "pr_list",
  "author": "me",
  "state": "OPEN",
  "limit": 10
}

# Get PR overview
{
  "action": "pr_overview",
  "pr": "123"
}

# Create PR with auto-reviewers
{
  "action": "pr_upsert",
  "title": "Add new feature",
  "source": "feature/my-branch",
  "destination": "main",
  "auto_reviewers": "default"
}

# Add inline comment
{
  "action": "pr_comment_add",
  "pr": "123",
  "text": "Consider extracting this to a helper function",
  "file": "src/main.py",
  "line": 42
}

# Add a comment that @mentions someone by display name
{
  "action": "pr_comment_add",
  "pr": "123",
  "text": "PTAL",
  "mentions": ["Jason Schulz"]
}

# Or write the mention directly where it belongs in the text
{
  "action": "pr_comment_add",
  "pr": "123",
  "text": "cc @[Jason Schulz] — the fix is on main."
}

@Mentions

Bitbucket accepts a comment containing a mention token for an account that doesn't exist and posts it happily -- @{some-bogus-id} renders as inert text and notifies nobody, with no error to signal the miss. pr_comment_add closes that gap:

  • mentions accepts a display name, nickname, or account id per entry. Each is resolved to a verified account id and prepended to the comment as Bitbucket's @{account_id} markup.
  • Any @{...} token already typed into text is verified the same way, even without using mentions.
  • If any mention doesn't resolve -- unknown, ambiguous between multiple members, or a fabricated id -- nothing is posted. The error lists each failed mention and, for an ambiguous name, the candidate members.
  • The response includes mentions_resolved (query, account_id, display_name per mention) so you can confirm what was actually stored without re-fetching the comment.
  • Resolution matches against workspace members by exact display name or nickname (Bitbucket's members API doesn't expose email addresses).

You can also write the mention directly into the body, where you want it to appear:

pr_comment_add(
    pr="123",
    text="cc @[Jason Schulz] — the fix is on main.",
)

Body references are handled by intent, same as the mentions parameter:

Written in the body Behavior
@[Full Name] Explicit — must resolve, or the comment is refused
@Name, @Name Surname Converted to a real mention when it matches exactly one workspace member
Anything that doesn't resolve Left as the literal text you wrote, and returned in mentions_unmatched
An incomplete @[... with no closing bracket Also returned in mentions_unmatched instead of being read as plain text — you meant to tag someone, so it's surfaced, not swallowed. It never blocks the post either, since there's nothing complete to refuse over.
Code spans, fenced code blocks Never touched

mentions_unmatched is the whole point: it turns "posted silently to nobody" into "named in the response," so a bare guess like @Sprint never fails a comment but also never notifies someone who doesn't exist without you knowing. Unlike the Jira MCP server, there's no email form here — Bitbucket's workspace members API doesn't expose email addresses, so bare-name resolution matches display name or nickname only.

Verification also checks account status: Bitbucket's API documents account_status as "active" today (its own schema notes more values may be added later), so a present-but-non-"active" status is treated as unresolvable rather than trusted — a future status change fails closed instead of posting a mention nobody receives.

Command-Line Interface (bb-cli)

The same action-based surface is available from the terminal via bb-cli. It runs a single action and prints the JSON result to stdout — clean to pipe into jq or consume from an agent with bash access. Diagnostic logs go to stderr (silence with --quiet), and the exit code is 0 on success, 1 on error (errors are emitted as parseable JSON).

# After `pip install -e .` the console script is on PATH:
bb-cli pr_list --author me --state OPEN --limit 5 --quiet | jq '.items'

# Or run the module directly without installing:
python cli.py pr_list --author me --state OPEN --limit 5 --quiet

Examples

# List open PRs for the current user
bb-cli pr_list --author me --state OPEN --limit 10

# PR overview / review
bb-cli pr_overview --pr 123
bb-cli pr_review --pr 123 --repo workspace/repo

# Add an inline comment (writes to Bitbucket)
bb-cli pr_comment_add --pr 123 --text "Extract this to a helper" --file src/main.py --line 42

# Add a comment that @mentions someone by display name (writes to Bitbucket)
bb-cli pr_comment_add --pr 123 --text "PTAL" --mentions "Jason Schulz"

# Batch create / resolve PR tasks
bb-cli pr_tasks_sync --pr 123 \
  --create '[{"text":"fix typo","file":"a.py","line":3}]' \
  --resolve 456,789

# Create a PR with auto-assigned reviewers
bb-cli pr_upsert --title "Add feature" --source feature/my-branch \
  --destination main --auto-reviewers default

# Workspace discovery and repo basics
bb-cli workspace_list --kind repos --limit 20
bb-cli repo_get --slug workspace/repo --pretty

# Summarize the latest failing pipeline (with log excerpts)
bb-cli pipe_fail_summary --include-logs

List-valued flags (--create, --resolve, --reviewers) accept either a JSON array or a comma-separated string. --state, --verbosity, --task-state, --kind, and --auto-reviewers are restricted to the choices shown in --help.

-h, --help

usage: bb-cli [-h] [--quiet] [--pretty] [--repo REPO] [--limit LIMIT]
              [--cursor CURSOR] [--verbosity {ids,summary,full}]
              [--author AUTHOR]
              [--state {OPEN,MERGED,DECLINED,SUPERSEDED,ALL}] [--pr PR]
              [--goal {quick,thorough}] [--policy {risk,style,both}]
              [--text TEXT] [--file FILE] [--line LINE] [--mentions MENTIONS]
              [--task-state {OPEN,RESOLVED,ALL}] [--create CREATE]
              [--resolve RESOLVE] [--title TITLE] [--source SOURCE]
              [--destination DESTINATION] [--summary SUMMARY]
              [--reviewers REVIEWERS] [--auto-reviewers {default,all,none}]
              [--close-source] [--draft]
              [--kind {repos,members,projects,reviewers}]
              [--workspace WORKSPACE] [--slug SLUG] [--pipeline PIPELINE]
              [--include-logs]
              {pr_list,pr_overview,pr_review,pr_comment_add,pr_comments_list,pr_tasks_list,pr_tasks_sync,pr_upsert,pipe_fail_summary,workspace_list,repo_get,me_whoami}

Run a single Bitbucket action and print its JSON result.

positional arguments:
  {pr_list,pr_overview,pr_review,pr_comment_add,pr_comments_list,pr_tasks_list,pr_tasks_sync,pr_upsert,pipe_fail_summary,workspace_list,repo_get,me_whoami}
                        Operation to perform.

options:
  -h, --help            show this help message and exit
  --quiet, -q           Suppress diagnostic logs on stderr.
  --pretty              Pretty-print the JSON result.
  --repo REPO           'workspace/repo' slug; falls back to configured
                        default.
  --limit LIMIT         Max items to return (1-50).
  --cursor CURSOR       Pagination cursor.
  --verbosity {ids,summary,full}
                        Response detail level.
  --author AUTHOR       Filter PRs by author nickname; 'me' for current user.
  --state {OPEN,MERGED,DECLINED,SUPERSEDED,ALL}
                        PR state filter.
  --pr PR               PR identifier, e.g. '123' or '#123'.
  --goal {quick,thorough}
                        Review depth.
  --policy {risk,style,both}
                        Finding categories to return.
  --text TEXT           Comment text.
  --file FILE           File path for inline anchoring.
  --line LINE           Line number for inline anchoring.
  --mentions MENTIONS   People to @mention: JSON array or comma-separated
                        display names/nicknames/account ids.
  --task-state {OPEN,RESOLVED,ALL}
                        Filter tasks by state.
  --create CREATE       Tasks to create: JSON array of {text,file,line}.
  --resolve RESOLVE     Task IDs to resolve: JSON array or comma-separated.
  --title TITLE         PR title.
  --source SOURCE       Source branch.
  --destination DESTINATION
                        Destination branch.
  --summary SUMMARY     PR description/summary.
  --reviewers REVIEWERS
                        Reviewer identifiers: JSON array or comma-separated.
  --auto-reviewers {default,all,none}
                        Auto-assign reviewers.
  --close-source        Close source branch on merge.
  --draft               Create/update as draft.
  --kind {repos,members,projects,reviewers}
                        Item type to list.
  --workspace WORKSPACE
                        Workspace slug.
  --slug SLUG           Repository slug or 'workspace/repo'.
  --pipeline PIPELINE   Specific pipeline ID/number to analyze.
  --include-logs        Include full log excerpts.

Each action uses a subset of the flags below; unused flags are ignored. Run a
destructive action (pr_comment_add, pr_tasks_sync, pr_upsert) only when you
mean it — they write to Bitbucket.

Server Instructions

The unified server includes comprehensive instructions that are automatically injected into the LLM's context. These instructions guide the LLM to:

  1. Follow optimal workflows

    • Example: Use pr_overview before pr_review to gather context
    • Example: Use pr_tasks_sync for batch task operations instead of individual calls
  2. Understand cross-action relationships

    • How pr_upsert with auto_reviewers="default" automatically fetches and assigns reviewers
    • When to use pipe_fail_summary after pr_overview shows failing checks
  3. Optimize performance

    • Prefer pr_overview over multiple separate API calls
    • Use batching with pr_tasks_sync for multiple tasks
    • Leverage pagination with appropriate limits
  4. Handle edge cases

    • Required vs optional parameters for each action
    • Parameter format variations (e.g., repo as "workspace/slug" or just "slug")
    • Error messages and resolutions

Comparison: Multi-Tool vs Unified

Multi-Tool Server (server.py)

# Three separate tool calls
response1 = call_tool("pr_overview", {"pr": "123"})
response2 = call_tool("pr_review", {"pr": "123"})
response3 = call_tool("pr_comment_add", {
    "pr": "123",
    "text": "LGTM",
})

Pros:

  • Fine-grained tool selection
  • Familiar pattern for traditional MCP clients
  • Can filter specific tools via ALLOWED_TOOLS

Cons:

  • Limited guidance on workflows
  • LLM must infer optimal sequences
  • Tool descriptions have space constraints

Unified Tool Server (unified_server.py)

# Same functionality, one tool interface
response1 = call_tool("bitbucket", {"action": "pr_overview", "pr": "123"})
response2 = call_tool("bitbucket", {"action": "pr_review", "pr": "123"})
response3 = call_tool("bitbucket", {
    "action": "pr_comment_add",
    "pr": "123",
    "text": "LGTM"
})

Pros:

  • Comprehensive server instructions guide optimal usage
  • Documented workflows (PR review, creation, debugging)
  • Performance optimization tips built-in
  • Cross-action relationships explained
  • Better for LLMs that benefit from detailed guidance

Cons:

  • Single tool interface (action-based)
  • Cannot selectively disable specific actions
  • Server instructions increase context size (~200 lines)

When to Use Unified Server

Choose the unified server when:

  • ✅ Your LLM client benefits from detailed contextual guidance
  • ✅ You want optimized workflows documented in server instructions
  • ✅ You prefer action-based tool invocation
  • ✅ You want to follow MCP server instructions best practices
  • ✅ You're working with LLMs that struggle with complex multi-tool orchestration

Choose the multi-tool server when:

  • ✅ You need fine-grained tool filtering
  • ✅ Your client has strict context size limits
  • ✅ You prefer traditional tool-per-operation pattern
  • ✅ You're integrating with existing MCP tooling

Implementation Details

The unified tool (src/modules/tools/unified.py) is a thin wrapper that:

  1. Accepts an action parameter and action-specific arguments
  2. Validates required parameters based on the action
  3. Routes to the underlying implementation functions
  4. Returns the same response format as the individual tools

Key point: The unified server doesn't duplicate code. It uses the exact same tool implementations as the multi-tool server, just exposed through a different interface.

# unified.py routes to existing implementations
match action:
    case BitbucketAction.PR_LIST:
        return await pr_list(...)  # Same function as multi-tool server
    case BitbucketAction.PR_OVERVIEW:
        return await pr_overview(...)
    # ... etc

Configuration

The unified server uses the same configuration system as the multi-tool server:

Environment Variables:

# Preferred: repository/workspace access token (Bearer auth). Best for
# headless/hosted deployments; takes precedence over username/app password.
# App passwords are deprecated by Atlassian.
BITBUCKET_ACCESS_TOKEN=your_access_token
BITBUCKET_USERNAME=your_username
BITBUCKET_APP_PASSWORD=your_app_password
BITBUCKET_WORKSPACE=workspace-name
BITBUCKET_REPO=repo-slug
FASTMCP_PORT=8000
FASTMCP_HOST=localhost

HTTP Headers (per-request):

X-BITBUCKET-WORKSPACE: override-workspace
X-BITBUCKET-REPO: override-repo
X-BITBUCKET-ACCESS-TOKEN: your_access_token
Authorization: Bearer <token>

Resources and Prompts

The unified server mounts the same resources and prompts as the multi-tool server:

Resources:

  • Repository information
  • Recent pipeline runs
  • Open pull requests
  • Workspace members
  • Available branches
  • Workspace projects

Prompts:

  • All prompts from the resources server are available

Testing

# Syntax validation
python -m py_compile unified_server.py
python -m py_compile src/modules/tools/unified.py

# Type checking (with mypy installed)
mypy unified_server.py --ignore-missing-imports

# Runtime test (requires dependencies)
python unified_server.py --help

Migration from Multi-Tool Server

If you're currently using the multi-tool server and want to migrate:

  1. Update your tool calls:

    # Before
    call_tool("pr_list", {"author": "me"})
    
    # After
    call_tool("bitbucket", {"action": "pr_list", "author": "me"})
    
  2. Update your server startup:

    # Before
    python server.py --port 8000
    
    # After
    python unified_server.py --port 8000
    
  3. No changes needed for:

    • Environment variables
    • HTTP headers
    • Resources
    • Prompts
    • Authentication

Benefits of Server Instructions Pattern

Following the MCP blog post recommendations:

Cross-feature relationships - Documents how actions interact ✅ Operational patterns - Specifies performance-optimized sequences ✅ Constraints and limitations - Clarifies rate limits and boundaries ✅ Model-agnostic language - Factual guidance without assumptions

The blog post testing showed 60% improvement in GPT models following optimal workflows when instructions were present.

Changelog

Unreleased

  • pr_comment_add verifies every @mention (via a new mentions parameter, and any @{...} token already typed into text) against Bitbucket before posting. Bitbucket accepts a mention token for an account that doesn't exist and posts it as inert, silent text -- an unresolved or ambiguous mention now blocks the whole comment instead. The response echoes what was actually stored via mentions_resolved. See src/utils/mentions.py.
  • pr_comment_add now also recognizes @[Full Name] and bare @Name / @Name Surname written directly into the comment body (not just the mentions parameter or a raw @{...} token). Explicit @[...] must resolve or the comment is refused; a bare guess that doesn't resolve is left as the literal text and reported in the new mentions_unmatched response field instead of failing the post. Code spans and fenced code blocks are never touched.

1.2.4

  • Fix me_whoami and workspace auto-discovery hitting GET /2.0/workspaces, which Atlassian removed in CHANGE-2770 (returns 410 Gone). Both now use the user-scoped replacement GET /2.0/user/workspaces.

1.2.3

  • Add bb-cli, a command-line wrapper over the unified tool surface.
  • Remove hardcoded credentials from config.py.

License

Same as the main project.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bb_mcp_server-1.5.1.tar.gz (77.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bb_mcp_server-1.5.1-py3-none-any.whl (72.5 kB view details)

Uploaded Python 3

File details

Details for the file bb_mcp_server-1.5.1.tar.gz.

File metadata

  • Download URL: bb_mcp_server-1.5.1.tar.gz
  • Upload date:
  • Size: 77.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for bb_mcp_server-1.5.1.tar.gz
Algorithm Hash digest
SHA256 50fb00a97b2b21d7b268899c059280133b79fcbe897c217f82764314c585ac60
MD5 a21cff91bc91a31d0eaeb4966df44833
BLAKE2b-256 44ba0ded2897ea40ab539439e222335d1b394028392d5e8c69a867884257f424

See more details on using hashes here.

Provenance

The following attestation bundles were made for bb_mcp_server-1.5.1.tar.gz:

Publisher: publish.yml on jasonpaulso/bitbucket-openapi-generated

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file bb_mcp_server-1.5.1-py3-none-any.whl.

File metadata

  • Download URL: bb_mcp_server-1.5.1-py3-none-any.whl
  • Upload date:
  • Size: 72.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for bb_mcp_server-1.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 928adec6ab2683972e49c05c940cce33d4bfb349ea6cc6e27a6fd97afb3b127c
MD5 c564ed526ad50633073e9ef2dee31a73
BLAKE2b-256 5e1ae79f529b45e0c4038c4250f7bac342be3052fe16ab7fd19cd6343cf35637

See more details on using hashes here.

Provenance

The following attestation bundles were made for bb_mcp_server-1.5.1-py3-none-any.whl:

Publisher: publish.yml on jasonpaulso/bitbucket-openapi-generated

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page