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 13 separate tools (pr_list, pr_overview, pr_review, etc.), the unified server exposes a single bitbucket_operation 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

server.py                            # Main entry point
├─ src/modules/tools/unified.py     # Single tool implementation
└─ Uses same infrastructure as tools_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

# Stdio (default) -- for Claude Desktop and other MCP clients.
# --host/--port are ignored unless --transport http is also passed.
python server.py

# HTTP -- --transport http is REQUIRED; without it the server starts in stdio
# and silently ignores --host/--port.
python server.py --transport http --host 0.0.0.0 --port 8000

# With environment variables (still needs --transport http; the env vars set the
# bind address, they do not select the transport)
FASTMCP_HOST=localhost FASTMCP_PORT=8000 python server.py --transport http

Tool Interface

Single Tool: bitbucket_operation

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
  • pipe_branch_heads - List branches whose head pipeline run failed
  • workspace_list - List repos, members, projects, or reviewers
  • repo_get - Get repository basics with PR sample
  • me_whoami - Get authenticated user identity plus the active credential type. Under a repository/workspace access token the user-context endpoints are forbidden, which it reports as identity_available: false rather than raising -- so it is safe to call, but use repo_get to verify a token-based setup.

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). Exit code is 0 on success, 1 on an action/API error, 2 on a usage error (bad action, bad flag value, missing positional) — stdout is always JSON ({"error": "...", "type": "..."} on failure) for all three, with one exception: -h/--help prints human-readable text and exits 0.

# 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

# Which branches are currently broken? (one call, no logs)
bb-cli pipe_branch_heads --repo workspace/repo --limit 30 --days 14

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.

Running many invocations in parallel

Each bb-cli run is one process doing one action, so a sweep across repos is a fan-out. Size the pool by the number of jobs, not by CPU count.

# 15 repos, one worker each
printf '%s\n' "${REPOS[@]}" | xargs -P 15 -I{} bb-cli pipe_branch_heads --repo {} --quiet

Measured over 15 repositories: one worker per repo finished in 1.42s against 2.20s for a CPU-count-sized pool -- roughly a third faster. The work is network latency plus a fixed interpreter-and-import startup, and neither is CPU-bound, so workers past the core count still overlap usefully instead of contending. (Measurement contributed by a parallel session driving the same CLI.)

Two limits to respect:

  • Bitbucket rate-limits per credential. A fan-out wide enough to trip it turns a fast sweep into a slow one with 429s, so keep the pool proportional to the job list rather than raising it indefinitely.
  • Interpreter startup is paid per invocation. For a sweep large enough that startup dominates, call the tools in-process instead of spawning a CLI per item.

-h, --help

usage: bb-cli [-h] [--quiet] [--pretty] [--repo REPO] [--limit LIMIT]
              [--cursor CURSOR] [--verbosity {ids,summary,full}]
              [--author AUTHOR] [--reviewer REVIEWER] [--involving INVOLVING]
              [--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] [--days DAYS]
              {pr_list,pr_overview,pr_review,pr_comment_add,pr_comments_list,pr_tasks_list,pr_tasks_sync,pr_upsert,pipe_fail_summary,pipe_branch_heads,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,pipe_branch_heads,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; pipe_branch_heads scans
                        runs, clamped 1-100).
  --cursor CURSOR       Pagination cursor.
  --verbosity {ids,summary,full}
                        Response detail level.
  --author AUTHOR       Filter PRs by author: 'me', a display name/nickname,
                        or an account id. Unset by default (lists the repo's
                        PRs from all authors) -- 'me' fails under
                        repository/workspace access-token auth, so pass it
                        explicitly.
  --reviewer REVIEWER   Filter PRs by reviewer: 'me', a display
                        name/nickname, or an account id.
  --involving INVOLVING
                        Filter PRs to where this person is author OR
                        reviewer: 'me', a display name/nickname, or an
                        account id. Combines with --author/--reviewer (if
                        also given) using AND, not OR.
  --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.
  --days DAYS           Activity window in days for pipe_branch_heads
                        (default 30).

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 (tools_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 (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
X-BITBUCKET-USERNAME: your_username
X-BITBUCKET-APP-PASSWORD: your_app_password

X-BITBUCKET-ACCESS-TOKEN is the way to supply a Bitbucket access token per-request. An inbound Authorization: Bearer header is not accepted as a Bitbucket credential and is never forwarded to api.bitbucket.org -- a bearer is ambiguous between the MCP transport token (MCP_AUTH_TOKEN) and a Bitbucket token, so forwarding it risked sending a platform credential to Atlassian. No inbound Authorization scheme is forwarded, Basic included -- see the CLAUDE.md auth notes for why "Basic is unambiguous" does not survive a gateway performing basic-auth in front of this server. Supply Bitbucket credentials per-request with X-BITBUCKET-USERNAME + X-BITBUCKET-APP-PASSWORD or X-BITBUCKET-ACCESS-TOKEN.

Installing a fresh release

uv will report no version of bb-mcp-server==<version> for a release that is already on PyPI, and --refresh is not enough to clear it. Use --no-cache:

uv tool install --force --no-cache bb-mcp-server==1.10.0

This has bitten three releases in a row, in two different environments, and it fails in the most misleading way available -- the resolver says the version does not exist while the simple index lists it and a direct wheel download succeeds. Confirm the installed version before trusting any measurement taken through the tool:

python -c "import importlib.metadata as m; print(m.version('bb-mcp-server'))"

Separately, https://pypi.org/pypi/<pkg>/json is CDN-cached and can report a just-published version as absent for a minute or so. https://pypi.org/simple/<pkg>/ is what installers actually read and is authoritative.

Credential scope: what an access token cannot reach

The access token above is the recommended credential, and it is not equivalent to a user login. A repository or workspace access token authenticates as the resource, not as a person, and Bitbucket refuses several endpoints outright for that authentication mechanism. This is not a permission you can grant on the token -- there is no scope that turns it on.

Blocked for a repository-scoped token Affects
GET /user me_whoami; --author me / --reviewer me / --involving me
GET /user/workspaces me_whoami's workspace list
GET /workspaces/{ws}/members workspace_list --kind members; @mention resolution by display name
Any repository outside the token's own scope pr_list / repo_get / pipe_* against another repo

GET /user returns 403 with "This API is not accessible by this authentication mechanism"; the workspace endpoints return 403 with "Your credentials lack one or more required privilege scopes".

If you set both an access token and an app password, the token wins by TYPE regardless of scope -- so a repository-scoped token shadows a broader app password and this whole table applies even though the credential that would have worked is right there. The server warns at startup and on first use, and me_whoami reports the ignored credential in ignored_credentials. Unset BITBUCKET_ACCESS_TOKEN to fall back to the pair.

Practical consequences when running with a repository token:

  • Use repo_get -- not me_whoami -- to confirm a token-based setup works. me_whoami no longer errors under a token (it reports identity_available: false and lists the forbidden endpoints in unavailable), but "no identity" is not the same answer as "the credential reaches Bitbucket", and only repo_get gives you the latter.
  • Pass --author / --reviewer / --involving an explicit display name or account id rather than me. me needs GET /user and cannot resolve.
  • --mentions by display name needs the members endpoint. Pass account ids directly, or use a user-context credential, if mention resolution is required.
  • A workspace-scoped token lifts the last row but not the first three.

This matters beyond configuration: it shapes what a test can observe. Four silently-wrong behaviours in pr_list survived a green suite partly because the me paths that would have exposed them returned 403 in the development environment and were skipped rather than exercised. If your credential cannot reach an endpoint, a passing run says nothing about the code behind it.

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 server.py
python -m py_compile src/modules/tools/unified.py

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

# Runtime test (requires dependencies)
python 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 tools_server.py --transport http --port 8000
    
    # After
    python server.py --transport http --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

1.13.1

  • pr_upsert update no longer resets a draft PR to ready. Every layer defaulted draft to False: the pr_upsert wrapper signature, the unified dispatcher (draft or False), and _pr_upsert_impl itself -- whose if draft is not None guard was dead by construction. So any update that touched only some other field (a summary edit, a title tweak) sent draft: false and silently flipped a draft PR to ready, notifying its reviewers. Measured live on 1.12.0. draft is now Optional end-to-end and lands in the PUT payload only when explicitly passed; an omitted draft on create sends no field, which is behaviour-identical (Bitbucket's default is non-draft). Regression tests cover both omission paths.

1.13.0

  • Bitbucket's PR draft flag was accepted on write and discarded on every read. pr_upsert took draft=True and sent it, and nothing afterwards would tell you it had worked -- not pr_list, not pr_overview, not pr_review, not repo_get's PR sample, not the open-PRs resource, and not the create's own response. Two fields= projections enumerate the keys they want and neither named draft, so Bitbucket left it off the wire; neither assembly site would have built the key had it arrived. It is now surfaced on all of them, as a passthrough nullable boolean -- null means the payload did not say, and a wrong-typed value fails validation rather than being coerced into a plausible one. pr_upsert echoes the stored value for the same reason it already echoes description: a write you cannot confirm from its own response is a write you have to go re-read.
  • Breaking: the four affected output schemas all set additionalProperties: false, so PRItem, PRItemFull, pr_overview.pr and pr_upsert each gained a draft property. Without that, every one of those tools would answer isError=True :: Output validation error on the tools_server.py surface. A consumer pinning the old response shape sees a new key.
  • Worth recording, because it is why this was reported as unconfirmable: the unified server.py surface registers bitbucket_operation with no output schema at all. It validates nothing, so a missing field produces no error there -- and the surface that would have failed loudly is not the one most callers use. The gap was visible only from the side that cannot complain about it.

1.12.0

  • me_whoami no longer fails under the credential this project recommends. Its two endpoints (GET /user, GET /user/workspaces) are user-context only, so a repository/workspace access token 403s on both -- and the server's own instructions advertised the tool as step 1 of PR creation, so following them produced an auth error on a healthy server. Two agent sessions used it as a liveness probe and read that 403 as an outage. One was right by luck; the two states were distinguishable only by parsing the error string. A caller who believes the MCP is down reaches for curl, and where a review gate matches on the MCP tool name, that ships a PR with no review and no signal. A 403 is now data -- auth, identity_available, unavailable -- and each endpoint degrades on its own. Anything that is not a 403 still raises; a 500 is not a credential regime. The PR-creation workflow now starts at repo_get, which works under every credential.
  • A narrower credential silently beat a broader one. Precedence is by credential TYPE, so BITBUCKET_ACCESS_TOKEN always wins over BITBUCKET_USERNAME/BITBUCKET_APP_PASSWORD -- including when the token is scoped to one repository and the app password is not. Nothing reported that a second credential was present and discarded, so every identity endpoint 403'd and the server looked broken; diagnosis took a local server and a Postman bisect, and the fix was unsetting one variable. The losing credential is now named at startup, once more on first use, and in me_whoami's ignored_credentials. Precedence itself is unchanged.
  • pr_list said "nothing matched" the same way it said "nothing exists". A filter that resolved and matched nothing returned items: [], next: null, items_partial: false, and a resolved_actors entry naming a real uuid -- measured against a repo with 15 open PRs. Every field a caller checks said the answer was complete. resolved_actors[*].matched now separates the two. It is page-scoped and says so. items_partial gained a description too, because its name promises "the result set is incomplete" and it actually means "this page collapsed duplicate reviewer fan-out rows".
  • The served author description was not stale deploy, it was a second copy. The hosted server serves the unified bitbucket_operation tool, which carried its own hand-written description; when the wording was corrected, only the standalone pr_list copy changed. Three copies existed (standalone, unified, CLI) and now share one constant, with a test that reads the live registry rather than enumerating wordings. The text no longer asserts which auth regime is active, since the same build runs under both.
  • Breaking: me_whoami and pr_list gained response fields under additionalProperties: false, and me_whoami's user is now nullable.

1.11.0

  • Every fastmcp.cloud deploy had been failing since 1.8.0, and nothing said so. The hosted build runs fastmcp inspect as a Docker build step, and resources_server._install_lazy_hooks() monkeypatches manager methods at import time. FastMCP dropped ResourceManager.list_resources, ResourceManager.list_resource_templates and PromptManager.list_prompts after 2.12 -- in 2.14 every listing path reaches the manager through get_resources/get_resource_templates/get_prompts instead -- so the unconditional getattr raised AttributeError and the build died. The hooks now skip a name the installed FastMCP does not have, which costs no coverage on 2.14 and keeps them working on 2.12.
  • The reason it survived nine builds is worth more than the fix. requirements.txt floats fastmcp>=2.12.1,<3; the cloud resolved 2.14.7 while every local venv sat on an older resolve, so no test could reproduce it. And the two pipelines are independent: tag-driven PyPI publishing kept succeeding, so releases looked healthy while the hosted server stayed frozen on its last good build -- which is why it still served 12 actions and no pipe_branch_heads. A new cloud-build-check workflow now runs the cloud's own command against a fresh resolve on every push and PR. Pinning that job to a lockfile would have reproduced the local blind spot rather than the cloud, so it deliberately does not. It also asserts the inspected surface is non-empty, since a hook that quietly stopped firing would still exit 0.
  • pr_upsert no longer re-adds reviewers when updating a PR. The auto-assignment default was applied unconditionally, because creating was computed after the reviewer block -- so an update that said nothing about reviewers still shipped a reviewers array in the PUT body, re-adding and re-notifying people a human may have deliberately removed. Callers had worked around it by never updating a PR at all. Saying nothing on an update now means exactly that; asking explicitly still works, via reviewers or auto_reviewers. This also makes update usable under a repository-scoped access token, since the auto path called GET /user, which 403s there.
  • The update path had no test of any kind -- five auto-reviewer tests, all of them creating. Two were added, and the regression one was checked against the pre-fix code to confirm it actually fails there rather than passing for its own reasons.
  • Failed transport auth now leaves an audit record. BearerAuthMiddleware refused unauthenticated requests without logging anything at all, so a rejected bearer produced no evidence from this codebase. Both refusal paths now emit a distinct WARNING. The messages are deliberately fixed literals -- the missing-vs-invalid distinction is carried by which literal is logged, never by interpolated detail, because a length or a prefix of a presented token is credential material and an audit log is read by more people than the credential is issued to.
  • Compatibility with fastmcp 2.14, which the hosted deploy now resolves. sse_app() was removed upstream (http_app(transport="sse") is the surviving spelling, verified to produce an identical app on both versions), and the test client factories gained **kwargs because 2.14 passes follow_redirects. Neither change touched an assertion.
  • A measured behaviour change worth knowing: initialize does not enter the middleware chain on 2.12.4 but does on 2.14.7 -- a spy middleware sees ['tools/list', 'tools/call'] on the former and ['initialize', 'tools/list', 'tools/call'] on the latter. Comments in auth.py and security.py asserted the 2.12 behaviour as though it were universal; they now state both readings, and a test pins them. Only those two versions were run, so the minor where it flipped is unknown and is described as unknown. The AuthProvider passed as auth= remains the layer that gates the handshake regardless of version, which is exactly why it stays.
  • Under 2.14 a middleware exception raised during a tool call is sanitised into a generic -32602 Invalid request parameters, so the two refusal messages no longer reach the client. Reviewed as neutral, not a regression: there is one static shared secret, so "missing" versus "invalid" tells a caller nothing it did not already know, and the timing channel is closed separately by hmac.compare_digest. Rather than reopen a verbatim middleware-string channel to unauthenticated clients, the two end-to-end tests now assert the property instead of the prose -- the request is refused, the tool never executed, our distinct record was logged, and no credential material reached it. The old assertion could not detect a tool that ran and only then raised; the new one does.

1.10.2

  • pr_tasks_list --task-state OPEN returned zero tasks, always. Bitbucket's task vocabulary is UNRESOLVED/RESOLVED; this tool's filter is spelled OPEN/RESOLVED/ALL, and the caller's spelling went into the query verbatim. state="OPEN" is a query Bitbucket accepts and that matches nothing, so the filter answered "no tasks" whether or not any existed. RESOLVED worked only because that literal coincides with Bitbucket's.
  • The failure direction is what makes it serious: --task-state OPEN answers "what tasks are blocking this PR?", and a caller could not distinguish a clean PR from a broken filter. The unfiltered call and --task-state ALL were correct throughout, including the computed open count.
  • Only the query is translated. The emitted state still passes through as Bitbucket sends it (UNRESOLVED), and a test pins that asymmetry so it is not "fixed" later -- rewriting the response would break callers reading it.
  • Found by exercising the one path this project could not reach on its own: no pull request in the workspace had a single task, so the filter had only ever been tested against mocks. A downstream consumer created one on a draft PR with explicit authorization, measured the failure, and removed it.

1.10.1

  • An inbound Authorization: Basic header is no longer forwarded to Bitbucket, and the documentation that said it was has been corrected. The docs were wrong in both directions: they described behaviour the code did not have on the real HTTP path -- get_config() discarded the request config before auth.py could see the header -- and the behaviour they described was not one worth having. "Basic is unambiguous, Bitbucket is its only consumer here" is true of this server's own consumers and false of the deployment chain: an operator fronting a transport-auth-less MCP server with gateway basic-auth would have every request carry the gateway password, which this server would forward to Atlassian. That is the same confused-credential class that got Bearer forwarding removed in 1.6.0, and it appears in exactly the deployment where Basic forwarding was reachable at all.
  • Nothing is lost: X-BITBUCKET-USERNAME + X-BITBUCKET-APP-PASSWORD is the exact equivalent and is unambiguous by construction. Basic forwarding was also only ever reachable on an HTTP server with no MCP_AUTH_TOKEN, since otherwise Authorization carries the transport bearer.
  • The credential-leak test now covers the refusal path as well as the log path: a refused header's value must not appear in logs or in the resulting ValueError. Basic joins the refused-scheme cases.

1.10.0

  • bb-cli starts ~3x faster: 0.267s to 0.084s. The CLI was importing fastmcp on every invocation for symbols its own code path never uses -- Context appears only in the decorated MCP wrapper signatures, and get_logger is a stdlib logger with extra steps. Measured with -X importtime, the import was ~200ms of a ~267ms startup; config init, load_dotenv and logging setup were never the cost. After the split, import cli pulls 374 modules instead of 926, with zero fastmcp and zero pydantic among them, and startup sits on the httpx+dotenv floor of ~0.075s.
  • The 13 context-taking tool wrappers and the unified tool moved to src/modules/tools/wrappers.py; implementations, helpers and enums stayed where they were. That direction was chosen deliberately over moving the impls: it preserves every patch("src.modules.tools.tools.make_request") target and the impl imports in nine test files. The v1.4.0 wrapper/impl split made this separation logical; this makes it physical.
  • config.py's get_context / get_http_headers / get_http_request are now lazy shims that delegate to fastmcp when it is loaded and otherwise reproduce its no-context behaviour, so the server path is unchanged.
  • A regression test asserts the property directly rather than its symptom: a child interpreter imports cli and fails if any fastmcp* module appears in sys.modules. A timing measurement alone would not prove the import is gone.
  • The header shim's delegation is covered by a test that drives fastmcp's real request context through httpx's ASGITransport. It was not: making the shim return {} unconditionally left all 350 tests green, because every other test passes headers explicitly. Config.__init__ calls that shim whenever headers are not passed, so an unnoticed change there would have silently ignored per-request X-BITBUCKET-* configuration on the HTTP transport.
  • Three behaviour deltas, none contract-breaking: CLI stderr diagnostics are plain-formatted rather than rich (same 13 lines, same order; server mode unaffected), submodules are no longer bound as attributes after a bare import src.modules.tools, and config.get_http_headers drops fastmcp's unused include_all parameter.

1.9.2

  • pr_list reports items_partial. Deduping OR-clause matches in 1.9.1 was necessary but traded a visible bug for an invisible one: Bitbucket's fan-out is consumed BEFORE the pagelen clamp, returning one row per reviewer (exactly max(1, len(reviewers)), measured live), so limit=10 against PRs with two reviewers each yields 10 rows and 5 distinct PRs. Collapsing them silently left a caller who asked for 10 holding 5, unable to tell whether the repo ran out or the window did. next signals that more exist but not why the page is short; items_partial says it, mirroring pipe_branch_heads' heads_partial.
  • Correct the mechanism described in 1.9.1's entry below. It attributed the duplication to Bitbucket matching the reviewer clause through participants. That was wrong: it is a join fan-out on the reviewers array, which is why an account that reviews nothing duplicates identically. Reported and measured by the same downstream consumer.

1.9.1

  • pr_list deduplicates OR-clause matches. Bitbucket returns one row per matching clause, so involving -- which builds (author.account_id=X OR reviewers.account_id=X) -- returned the same PR twice. Measured live: 20 rows for 11 distinct PRs. The mechanism is a join fan-out on the reviewers array, not clause matching: rows come back as exactly max(1, len(reviewers)) per PR, so an account that reviews nothing fans out identically and only the OR combination triggers it. Wrong since involving shipped in 1.7.0. Reported by a downstream consumer whose own REST implementation hit it the same day.
  • workspace_list --kind repos sorts by recency. Bitbucket's default ordering is not recency -- live, page 1 led with a repo last updated in May 2025 while the workspace's newest had changed minutes earlier -- so "the N most recently active repos" required fetching every page and sorting locally. Now one call.
  • Malformed entries degrade in every tool, not just pr_list. 1.9.0 said a bare string where an object belongs is dropped rather than crashing. That was true of pr_list and of nothing else: workspace_list (all four kinds), pr_tasks_list, pr_tasks_sync, me_whoami, pipe_fail_summary, pr_overview and pr_review all still raised AttributeError, reaching the caller as Error calling tool .... The 1.9.0 changelog entry overstated its scope; this is the fix that makes it true.
  • Fix two crashes on a present-and-null string: pr_review on a null PR title (where pr_overview on the identical payload correctly returned a schema violation -- the two disagreed), and pr_tasks_sync with create=[{"text": null}], which is caller-controlled.
  • pr_list's reviewers no longer emits an empty-string uuid. Tightening the filter to reject a non-string uuid in 1.9.0 accidentally admitted "", which the previous truthy check had excluded.
  • Docs named a tool that does not exist. The unified server registers bitbucket_operation; README and CLAUDE.md both said bitbucket, so a client following the documentation got "Unknown tool". A test now asserts every documented tool name is actually registered on one of the two surfaces.
  • Test coverage for the ~42-site null-safety rewrite, which had none. Mutation testing showed six of those guards -- including the mainbranch crash fix -- could be reverted with the whole suite still green. A present-and-null fixture direction now exercises every tool.
  • Remove a vacuous test case (pr_tasks_list in the boolean-hazard set produced byte-identical output with and without the poison) and correct two fixture assertions that were false against live data: Bitbucket nulls old on an added file, and target.ref_name is absent from most pipelines.

1.9.0

  • Breaking: pr_overview's diffstat_top entries rename del to remove. del is a Python keyword, so a client building a model from the declared response schema cannot create a field for it and logs a parse error on every pr_overview call. To be precise about what this does and does not fix: the response data itself was never wrong -- structured content is present and correct either way -- so this removes client-side error noise, it does not repair broken output. A caller reading diffstat_top[].del must read [].remove instead.

  • pr_list summary items now carry comments (Bitbucket's comment_count) and reviewers (requested reviewers by uuid). Neither was reachable before at any verbosity: the request's own fields= projection filtered them out, so raw.comment_count was None even at verbosity="full". A downstream tool badging PR comment activity read every PR as zero comments, with no error anywhere -- verified against live data where 9 open PRs carried comments and all would have reported 0.

  • pr_list responses include resolved_actors, echoing what each author/reviewer/involving filter resolved to (uuid for "me", account_id for a name). A caller filtering by "me" can now read its own identity from the same response instead of issuing a GET /user -- the call that 403s under access-token auth. {} when no actor filter was given and on cursor requests; the key is always present.

  • "me" is resolved at most once per pr_list call. --author me --reviewer me --involving me previously issued three separate GET /user requests for the same identity.

  • Fix pr_list and repo_get on the tools_server.py surface. Every tool there declares an output_schema with additionalProperties: false, which FastMCP enforces -- so adding comments/reviewers/resolved_actors broke both tools for every call (isError: Output validation error), including empty result sets that had validated before. repo_get was hit because recent_prs reuses the same PRItem definition. The unified server.py surface was never affected, which is why it went unnoticed.

  • Fix five further output-schema violations that were already there and had never been reported, all on the same surface: pr_list with verbosity="ids" (emits integers, schema allowed only strings) and verbosity="full" (emits raw, which PRItem forbids); pr_overview (pr.description, comments_recent[].text); pr_review (goal, policy, findings[].category); pr_tasks_list (Bitbucket's UNRESOLVED state, and null created/updated/creator/url); repo_get (name, description).

  • Fix pr_tasks_sync and pr_upsert, which returned isError on EVERY call on the tools_server.py surface: created_tasks and description are unconditional members of their return dicts and neither was declared (partial/failures were undeclared too). Both were pre-existing.

  • Fix pipe_fail_summary, which failed validation on live payloads whenever a log excerpt exceeded 20 lines -- the normal case for the tool's purpose. It adds top_lines_truncated (and logs_partial/logs_error on a failed log fetch); none were declared.

  • Declare nullability where the impl can genuinely emit null, and only there. Bitbucket omits optional fields routinely and the impls pass the resulting None through, so a task without a timestamp or a repository without a main branch must validate. But fields the API always returns -- a PR's state, a repository's slug, the title/state echoed back from a write you just performed -- stay non-nullable: a null there means the response was not what was asked for, and that should fail loudly rather than validate. No field is now both required and nullable, which is not a contract but a contradiction ("must be present, may be meaningless"); there were ten.

  • Fix a whole crash class, not one instance of it. X.get("key", {}).get("sub") is unsafe because a get() default applies only when the key is ABSENT, and Bitbucket returns keys present-and-null (mainbranch: null for a repo with no commits, links: {"html": null}). 42 such chains across tools.py, meta.py and the resources modules now use (X.get("key") or {}). Two were proven to crash and reach the caller as Error calling tool ...; the rest were unaudited and are now moot. The first attempt at this fixed a single site and left an identical one 300 lines away in the same file.

  • Degrade instead of crashing on malformed values. A bare string where a PR object belongs is dropped rather than becoming a null-filled item, and garbage participants/reviewers/links entries no longer raise. A wrong-typed scalar inside a real PR object is deliberately NOT coerced: it fails validation, because it means the response was not what was asked for.

  • Reject booleans in integer fields. isinstance(True, int) is True in Python (bool subclasses int) while JSON Schema treats them as disjoint types, so a boolean comment_count passed every guard and then failed output validation. Guards now use an explicit int-not-bool check.

  • Tighten $defs.Step from additionalProperties: true, which made pipeline step content structurally unvalidatable -- nothing about a step could fail, mocked or live.

  • New tests/test_output_schema_contract.py drives every tool's impl and validates the result against its own declared schema. Nothing did this before -- the existing schema test checks input parameter descriptions only, so the declared output contracts were never exercised against real returns. It runs every tool against both a rich and a near-empty fixture, because those ask different questions: a rich fixture asks "is every emitted field declared?", and only a thin one asks "is every declared type honest when the source data is missing?". The thin pass found 11 further violations. All 13 tools are covered with no exemptions.

  • Documentation defaults are now checked against the live schema rather than by searching for wordings: the bundle's structured (optional, default: X) entries must match, and any line in the server instructions that asserts a default must agree with it. This closes the author="me" claim, which turned out to exist in a third place (the bundle's SKILL.md) after being fixed in two others.

  • Correct the unified server's pr_list guidance, which told the model author="me" was "default behavior". It has not been since 1.7.0, when the default was removed for 403ing under access-token auth -- the same defect already fixed in the skill bundle in 1.8.1, in a second place.

1.8.1

  • Fix the --quiet authlib deprecation warning for the second time. The previous fix matched on the message instead of module= (correct, and necessary), but was still defeated by ordering: authlib/deprecate.py runs warnings.simplefilter("always", AuthlibDeprecationWarning) at import time, and simplefilter inserts at the front of warnings.filters. Imported lazily through fastmcp, it landed ahead of our filter and re-armed the warning. cli.py now imports authlib.deprecate first so our filter goes in front. This is also why PYTHONWARNINGS and warnings.catch_warnings() both failed to suppress it. Verified 394 bytes of stderr to 0 against authlib 1.7.2; the import is load-bearing and must not be removed as unused.
  • Document credential scope: what a repository-scoped access token cannot reach (GET /user, /user/workspaces, workspace members, other repos), and which flags that rules out.
  • Document sizing for parallel bb-cli fan-out (one worker per job, not per core) with the measurement behind it.
  • Regenerate the README --help reference, which was missing pipe_branch_heads, --reviewer, --involving, and --days.
  • Add pipe_branch_heads to the unified server's instruction block, the skill bundle, and the documented action lists. It shipped in 1.8.0 registered and routable but unmentioned in all ~200 lines of instructions, so a model driving the bitbucket tool had no way to discover it. A new test asserts every registered action appears in the instructions.
  • Backfill this changelog, which stopped at 1.2.4 while six releases shipped.
  • Correct the skill bundle against the live schema. It documented pr_list's author as defaulting to "me" -- a default removed in 1.7.0 precisely because resolving it costs a GET /user that 403s under access-token auth -- and omitted pr_list's reviewer/involving and pr_comment_add's mentions entirely. Also drops an unmodified scaffolding placeholder (references/api_reference.md) that pointed at skills not present here.
  • Correct .claude/PROGRESS.md, which had claimed 11 tools since 2025-11 and listed neither pr_tasks_list nor pipe_branch_heads.
  • Tests now pin the documentation surface against the registry rather than against wording: every action is named in each doc and has a bullet in the bundle's SKILL.md and an entry in its Action Index; every live parameter appears in its own action's section; total counts must match the registry while group subtotals must sum to it.

1.8.0

  • pipe_branch_heads: report the head pipeline of every branch with recent activity, so a failing branch is visible without one call per branch. Returns heads_partial when the scan window cut off before every branch was seen, rather than implying completeness.
  • Stop the resources mount re-fetching on every tool call. resources_server declared a lifespan=, which made FastMCP mount it as a proxy; tool routing then enumerated every mounted server and re-entered that lifespan per call. Seven HTTP requests and ~1.9s per tool call became one and ~0.31s.
  • First --quiet authlib fix (see 1.8.1 -- it was necessary but not sufficient).

1.7.0

  • Breaking: pr_list builds a single q expression from AND-ed predicates. Bitbucket ignores the state parameter whenever q is present, so --state was silently dropped for any filtered query. state is now folded into q and never sent separately. Adds --reviewer and --involving.

1.6.0

  • Stop forwarding an inbound Authorization: Bearer header to api.bitbucket.org. A bearer is ambiguous between the MCP transport token (MCP_AUTH_TOKEN) and a Bitbucket token, so forwarding it risked handing a platform credential to Atlassian. Use X-BITBUCKET-ACCESS-TOKEN instead.
  • MCP_AUTH_TOKEN now guards the HTTP transport only, keyed on the absence of an HTTP request rather than an empty header dict -- setting it no longer bricks stdio, and an HTTP request sending no headers is still rejected.
  • REQUIRE_CLIENT_CREDENTIALS=true also blocks env fallback for a request that omits all X-BITBUCKET-* headers, which previously bypassed it.
  • Constant-time comparison (hmac.compare_digest) for the transport token; stop logging credential length; close the set of loggable auth schemes so a schemeless header cannot leak its value into a DEBUG log.
  • Gate initialize, honour NO_TOOLS, and report the real package version.
  • Fix error classification (timed out was unmatched), raw-payload mispairing in workspace_list, and an N+1 issuing GET /user per reviewer.
  • Make the CLI's documented exit-code and JSON contract true: argparse usage errors now emit JSON on stdout and exit 2 instead of writing to stderr with empty stdout.
  • Correct documented behaviour the code never had.

1.5.1

  • Declare pytest-asyncio so the suite runs on a clean checkout.

1.5.0

  • 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.4.0

  • Add BITBUCKET_ACCESS_TOKEN for non-interactive Bearer auth.
  • Complete the wrapper/impl split so the CLI and MCP share one code path.
  • Register pr_comments_list and pr_tasks_list; correct the tool count.
  • Stop discarding response content in the formatter; stop rebinding unified.bitbucket to its Tool wrapper.
  • pr_tasks_sync reports partial failures and echoes created tasks.
  • Keep the no-eligible-reviewers log at info, not warning.

1.3.1

  • Drop deferred annotations so fastmcp 2.14 can build tool schemas.

1.3.0

  • Add the bb-cli terminal wrapper; remove hardcoded credentials.
  • Add pr_comments_list and pr_tasks_list actions.
  • Migrate off the removed /2.0/workspaces endpoint (CHANGE-2770).
  • Bound fastmcp to <3 to avoid an async Context API break.

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.

Release files for bb-mcp-server 1.13.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bb-mcp-server 1.13.1
File Size Uploaded
bb_mcp_server-1.13.1.tar.gz 220.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bb-mcp-server 1.13.1
File Interpreter ABI Platform
bb_mcp_server-1.13.1-py3-none-any.whl Python 3 none any Details

Total release size: 335.6 kB

Release files / bb_mcp_server-1.13.1.tar.gz

Download URL bb_mcp_server-1.13.1.tar.gz
Size 220.5 kB
Tags Source
SHA-256 checksum
How to use checksums
0047bc40ff5933c86f9ea6af48d677d4bbb2b34831efad652796786a734112d8
BLAKE2b-256 checksum
How to use checksums
0407fce800841eb429f522a07ac8273d59800a315649473b18298a41159c1286
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release files / bb_mcp_server-1.13.1-py3-none-any.whl

Download URL bb_mcp_server-1.13.1-py3-none-any.whl
Size 115.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b5b21c5b1728d3062f3d91b05e624cb02d27989f678a20eec5638f967510dcb1
BLAKE2b-256 checksum
How to use checksums
41d114250805367f4f379e7d779a9a537e9b533a12a42140f8fdcbf9178457b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.13.1 This release

2 release files

1.13.0

2 release files

1.12.0

2 release files

1.11.0

2 release files

1.10.2

2 release files

1.10.1

2 release files

1.10.0

2 release files

1.9.2

2 release files

1.9.1

2 release files

1.9.0

2 release files

1.8.1

2 release files

1.8.0

2 release files

1.7.0

2 release files

1.6.0

2 release files

1.5.1

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.3

2 release files

1.2.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page