From 30f32a88e8e0c9a6bd8d67beffc7c9bb9084afd6 Mon Sep 17 00:00:00 2001 From: drknowhow Date: Fri, 23 Oct 2026 07:39:14 -0400 Subject: [PATCH] fix(bitbucket): list_prs takes author and reviewer (2.158.3) The handler and client could filter list_prs by author and reviewer, but the c3_bitbucket MCP signature lacked both, so pydantic refused them with "Unexpected keyword argument". Work Agents' Bitbucket watchers failed on every poll this way. The tool now takes both and passes them through. Full suite: 4990 passed, 8 skipped (4 files run with tests/ forced first: Hershey_Fonts installs a site-packages tests package that shadows ours). Claude-Session: https://claude.ai/code/session_018s3QLHP63kNnNysJhvthv8 --- CHANGELOG.md | 13 +++++++++++++ cli/c3.py | 2 +- cli/mcp_server.py | 6 +++++- pyproject.toml | 2 +- tests/test_bitbucket_tool.py | 20 ++++++++++++++++++++ 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2db89f..6d7ee45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to Code Context Control (C3) are documented here. The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.158.3] - 2026-10-23 + +### Fixed — `c3_bitbucket list_prs` takes `author` and `reviewer` + +The handler and the Bitbucket client could already filter `list_prs` by +author and by reviewer (Bitbucket's `role.N`/`username.N` query), but the +MCP tool's signature had neither argument, so a client that passed one was +refused with pydantic's "Unexpected keyword argument" before the handler ran. +Work Agents' Bitbucket watchers and its Work page asked for "my pull +requests" that way and failed on every poll. `c3_bitbucket` now takes +`author` and `reviewer` (Bitbucket user names) and passes them through, in +text and in `format="json"`. + ## [2.158.2] - 2026-10-21 ### Fixed — approval requests that never reached the desk or the phone diff --git a/cli/c3.py b/cli/c3.py index 0c44411..fd8e630 100644 --- a/cli/c3.py +++ b/cli/c3.py @@ -91,7 +91,7 @@ # Config CONFIG_DIR = ".c3" CONFIG_FILE = ".c3/config.json" -__version__ = "2.158.2" +__version__ = "2.158.3" # The PreToolUse matcher for native subagent calls (installer and hub migration). AGENT_MATCHER = "Agent|Task" diff --git a/cli/mcp_server.py b/cli/mcp_server.py index 0922868..88158b7 100644 --- a/cli/mcp_server.py +++ b/cli/mcp_server.py @@ -1277,6 +1277,8 @@ async def c3_bitbucket( pr_id: int = 0, branch: str = "", state: str = "OPEN", + author: str = "", + reviewer: str = "", title: str = "", body: str = "", from_branch: str = "", @@ -1304,7 +1306,8 @@ async def c3_bitbucket( default branch, browse and clone URLs), readme ([at=branch/commit] — the root README, markdown preferred), get_file (path [at] — one text file, 256 KB cap, binary refused), - list_prs, get_pr, get_pr_diff, get_pr_activities, get_pr_commits, + list_prs ([author] [reviewer] — Bitbucket user names), get_pr, + get_pr_diff, get_pr_activities, get_pr_commits, pr_issues (pr_id — Jira issues Bitbucket's Jira integration links to the PR), create_pr, update_pr (pr_id + any of title/description/reviewers/to_branch — unchanged fields are preserved; reviewers is the FULL comma-separated @@ -1335,6 +1338,7 @@ def finalize(fname, fargs, fresp, fsumm, **kw): return await asyncio.to_thread( handle_bitbucket, action, svc, finalize, project=project, repo=repo, pr_id=pr_id, branch=branch, state=state, + author=author, reviewer=reviewer, title=title, body=body, from_branch=from_branch, to_branch=to_branch, description=description, reviewers=reviewers, name=name, url=url, events=events, start_point=start_point, commit=commit, diff --git a/pyproject.toml b/pyproject.toml index 6b61f85..037f257 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "code-context-control" -version = "2.158.2" +version = "2.158.3" description = "Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub." readme = "README.md" requires-python = ">=3.10" diff --git a/tests/test_bitbucket_tool.py b/tests/test_bitbucket_tool.py index a9f4edf..5ffa74f 100644 --- a/tests/test_bitbucket_tool.py +++ b/tests/test_bitbucket_tool.py @@ -1,11 +1,13 @@ """Tests for cli/tools/bitbucket.py — handle_bitbucket dispatch.""" from __future__ import annotations +import asyncio import json import sys import tempfile import unittest from pathlib import Path +from types import SimpleNamespace from unittest import mock REPO_ROOT = Path(__file__).resolve().parent.parent @@ -456,6 +458,24 @@ def test_list_branches_passes_filter_and_order(self): self.assertEqual(kw["filter_text"], "feat") self.assertEqual(kw["order"], "modification") + def test_list_prs_passes_author_and_reviewer(self): + finalize, _ = _captured_finalize() + tool.handle_bitbucket("list_prs", self.svc, finalize, author="alice", reviewer="bob") + kw = next(c[2] for c in self.client.calls if c[0] == "list_prs") + self.assertEqual((kw["author"], kw["reviewer"]), ("alice", "bob")) + + +class TestBitbucketMcpTool(unittest.TestCase): + def test_list_prs_author_and_reviewer_reach_the_handler(self): + from cli import mcp_server + schema = asyncio.run(mcp_server.mcp.get_tool("c3_bitbucket")).parameters["properties"] + self.assertLessEqual({"author", "reviewer"}, set(schema)) + ctx = SimpleNamespace(request_context=SimpleNamespace(lifespan_context=object())) + with mock.patch.object(tool, "handle_bitbucket", return_value="ok") as handle: + asyncio.run(mcp_server.c3_bitbucket("list_prs", author="alice", reviewer="bob", ctx=ctx)) + self.assertEqual((handle.call_args.kwargs["author"], handle.call_args.kwargs["reviewer"]), + ("alice", "bob")) + def test_cap_clamps_single_overlong_line(self): # Issue 7: one line with no newlines must still be clamped by chars. long_line = "x" * 200_000