Skip to content

feat: implement MCP resource for public bounty board data #791#828

Open
Angelebeats wants to merge 4 commits into
ramimbo:mainfrom
Angelebeats:feat-mcp-bounty-export
Open

feat: implement MCP resource for public bounty board data #791#828
Angelebeats wants to merge 4 commits into
ramimbo:mainfrom
Angelebeats:feat-mcp-bounty-export

Conversation

@Angelebeats
Copy link
Copy Markdown

@Angelebeats Angelebeats commented Jun 2, 2026

This PR implements the MCP (Model Context Protocol) resource for public bounty board data, as requested in issue #791. It exposes the bounty board data via the mcp://bounties/active resource.

Summary by CodeRabbit

  • New Features
    • MCP resources support: retrieve/read the active bounty board (bounties://active) including claimable/opening-soon buckets and treasury capacity via the MCP API.
  • Chores
    • Improved datetime/UTC compatibility across modules for broader Python version support.
  • Tests
    • Added API tests covering resources/list, resources/read, empty-state, and validation/error cases.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds MCP resource support: resource type/registry, JSON-RPC routing for resources/list and resources/read, a bounties://active resource fetcher that queries and summarizes active bounties, endpoint wiring to pass the resource handler into /mcp, UTC import compatibility fixes, and tests covering happy and error paths.

Changes

MCP Resources Support

Layer / File(s) Summary
MCP Resource Contract
app/mcp.py
MCPResourceHandler callable type and MCP_RESOURCES registry define bounties://active JSON resource.
MCP Request Handler Resources Routing
app/mcp.py
handle_mcp_request signature now accepts call_resource; adds resources/list (returns registry) and resources/read (validates params, requires uri, invokes handler, returns contents or mapped error); defaults tools/call arguments to {} when absent.
Active Bounties Resource Handler
app/mcp_tools.py
call_mcp_resource(database_url, uri) supports bounties://active: queries newest open bounties (cap 100), partitions into claimable_now/opening_soon, computes aggregated active liabilities via SQL SUM, reads treasury balance, and returns assembled JSON; also adjusts list_bounties candidate cap and simplifies bounty selection.
Endpoint Wiring
app/main.py
/mcp route imports call_mcp_resource and passes it alongside call_mcp_tool into handle_mcp_request.
UTC import compatibility
app/bounty_attempts.py, app/github_bounty_board.py, app/ledger/service.py, app/models.py, app/serializers.py, app/treasury.py, app/treasury_executor.py, tests/test_api_mcp.py
Modules now attempt from datetime import UTC and fall back to timezone.utc when datetime.UTC is unavailable.
MCP resource tests
tests/test_mcp_board.py
Adds test_mcp_bounty_board_resource, test_mcp_board_empty, and test_mcp_read_resource_errors to validate resources list/read content and params/uri error handling.

Possibly related PRs

  • ramimbo/mergework#398: Prior MCP dispatch wiring changes touching app/main.py and app/mcp_tools.py.
  • ramimbo/mergework#732: Overlaps on bounty selection and issue-number handling refactors in app/mcp_tools.py.
  • ramimbo/mergework#329: Earlier extraction/evolution of handle_mcp_request; related changes to MCP transport wiring.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal but states the core objective (implementing MCP resource for bounty board data per issue #791). However, it omits the required template structure including summary details, evidence sections, test evidence checklist, and MRWK tracking information. Expand the description to follow the repository template: add detailed summary bullets, evidence of the problem addressed, intended surfaces, expected PR size, out-of-scope items, and complete the test evidence checklist with MRWK tracking.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concretely names the primary change: implementing MCP resource support for bounty board data, directly corresponding to the main feature additions across app/mcp.py, app/mcp_tools.py, and related modules.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Mergework Public Artifact Hygiene ✅ Passed PR contains no investment, price, cash-out, or fabricated payout claims. New MCP resource description is factual and no documentation contradicts MRWK's native project coin definition.
Bounty Pr Focus ✅ Passed PR implements MCP resource for bounties://active with proper test coverage for all selector validation cases. UTC compatibility changes are necessary for imported modules.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 170d27d8-5e15-45af-bff6-690e833667be

📥 Commits

Reviewing files that changed from the base of the PR and between 12ca2f0 and 75dc5e6.

📒 Files selected for processing (3)
  • app/main.py
  • app/mcp.py
  • app/mcp_tools.py

Comment thread app/main.py Outdated
Comment thread app/mcp_tools.py Outdated
Comment on lines +291 to +297
def call_mcp_resource(database_url: str, uri: str) -> str:
if uri == "bounties://active":
with session_scope(database_url) as session:
query = select(Bounty).where(Bounty.status == "open").order_by(Bounty.id.desc())
bounties = session.scalars(query).all()
return json.dumps(bounties_to_dict(bounties, session=session))
raise ValueError("unknown resource")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add tests for the new resource handler.

No test covers call_mcp_resource (the bounties://active happy path and the ValueError("unknown resource") branch). As per coding guidelines: "Add or update tests for changed behavior".

Want me to draft the tests for both branches?


⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unbounded query: bounties://active returns every open bounty with no cap.

Unlike the list_bounties tool (Lines 113-119, 155), this handler applies no limit. On a public, unauthenticated /mcp endpoint the response size and the per-row pending-proposal preload in bounties_to_dict both scale with the number of open bounties, so a large board yields a large payload and query cost on every read.

Proposed fix: bound the result set
 def call_mcp_resource(database_url: str, uri: str) -> str:
     if uri == "bounties://active":
         with session_scope(database_url) as session:
-            query = select(Bounty).where(Bounty.status == "open").order_by(Bounty.id.desc())
+            query = (
+                select(Bounty)
+                .where(Bounty.status == "open")
+                .order_by(Bounty.id.desc())
+                .limit(100)
+            )
             bounties = session.scalars(query).all()
             return json.dumps(bounties_to_dict(bounties, session=session))
     raise ValueError("unknown resource")

If unbounded output is intentional for this resource, consider exposing pagination instead of returning the full table.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def call_mcp_resource(database_url: str, uri: str) -> str:
if uri == "bounties://active":
with session_scope(database_url) as session:
query = select(Bounty).where(Bounty.status == "open").order_by(Bounty.id.desc())
bounties = session.scalars(query).all()
return json.dumps(bounties_to_dict(bounties, session=session))
raise ValueError("unknown resource")
def call_mcp_resource(database_url: str, uri: str) -> str:
if uri == "bounties://active":
with session_scope(database_url) as session:
query = (
select(Bounty)
.where(Bounty.status == "open")
.order_by(Bounty.id.desc())
.limit(100)
)
bounties = session.scalars(query).all()
return json.dumps(bounties_to_dict(bounties, session=session))
raise ValueError("unknown resource")

Comment thread app/mcp.py Outdated
Comment thread app/mcp.py
Copy link
Copy Markdown
Contributor

@keilogic keilogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 75dc5e60bda9f0ef507af2cce39c134a5e905a08 against base 12ca2f0510abce4f3a8b3d77ae8596f47fa5c74c.

I am requesting changes for two current-head blockers:

  • resources/read reads params.get("uri") before confirming params is an object. I reproduced AttributeError for JSON-RPC requests with "params": [], null, or a string, so the public /mcp path can 500 instead of returning a JSON-RPC -32602 error. This should follow the tools/call params validation pattern and have regression coverage.
  • The PR is not passing required quality checks. Hosted Quality, readiness, docs, and image checks fails because ruff format --check . would reformat app/main.py, app/mcp.py, and app/mcp_tools.py; local targeted Ruff check also reports import ordering and an E501 in app/mcp.py, and git diff --check origin/main...HEAD reports a blank line at EOF in app/mcp_tools.py.

Other validation:

  • pytest tests/test_mcp_tools.py tests/test_api_mcp.py -q -> 108 passed, 1 warning
  • python scripts/docs_smoke.py -> docs smoke ok
  • git merge-tree --write-tree origin/main HEAD -> clean tree 6f01cd9ec2d619824747778e6e884419c6e27a2c
  • CodeRabbit is successful but has actionable comments on this same head.

I kept this review to public PR code, public GitHub checks, and local validation; no private data or payment/treasury/wallet execution was involved.

Copy link
Copy Markdown
Contributor

@gshaowei6 gshaowei6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 75dc5e60bda9f0ef507af2cce39c134a5e905a08 against the #791 proposal. I am requesting changes for an acceptance-scope gap that is separate from the existing params/formatting blockers.

#791 asks for one machine-readable bounty-board routing payload: live claimable rows plus pending create-bounty opening windows, treasury capacity/source URLs, and guidance that opening-soon issues are not claimable yet. The current implementation exposes bounties://active, but call_mcp_resource() only queries Bounty.status == "open" and serializes bounties_to_dict(...). A direct /mcp resources/read probe on this head returns a top-level JSON list, not a board object, and the payload has no claimable_now, opening_soon, treasury capacity, or source URL fields.

Local evidence:

  • TMP/TEMP pointed inside the worktree, python -m pytest tests/test_mcp_tools.py tests/test_api_mcp.py -q -> 108 passed.
  • Direct MCP probe: resource_uri=bounties://active, payload_type=list, has_claimable_now=False, has_opening_soon=False.
  • rg found no tests/docs for the new MCP resource and no use of treasury_status/pending create-bounty data in the resource implementation.
  • python scripts/docs_smoke.py -> docs smoke ok.
  • git merge-tree --write-tree origin/main HEAD -> clean tree 661b823d01b90517ee089553832ef394c6b81876.
  • Existing hygiene failures remain: targeted Ruff reports import ordering plus an E501, ruff format --check would reformat the three touched files, and git diff --check reports a blank line at EOF in app/mcp_tools.py.

Suggested direction: make the resource return the board shape from #791, backed by both open bounty rows and treasury pending-create/status data, with tests for at least one claimable row, one opening-soon row, and the no-pending-create case. The resource URI/name should also match the documented board semantics rather than only "active bounties".

@ramimbo ramimbo added the mrwk:needs-info More information needed label Jun 3, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/mcp.py (1)

13-13: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Serialize dict resource payloads before assigning contents[].text.

MCPResourceHandler says a resource may return dict[str, Any], but this branch copies that value straight into text. That makes the response shape depend on the handler implementation and will break callers that expect text to stay a JSON string. Either JSON-encode dict results here or narrow MCPResourceHandler to str only.

Proposed fix
-            content = call_resource(database_url, uri)
+            content = call_resource(database_url, uri)
+            text = json.dumps(content) if isinstance(content, dict) else content
             return {
                 "jsonrpc": "2.0",
                 "id": response_id,
                 "result": {
-                    "contents": [{"uri": uri, "mimeType": "application/json", "text": content}]
+                    "contents": [{"uri": uri, "mimeType": "application/json", "text": text}]
                 },
             }

Also applies to: 144-150

app/mcp_tools.py (1)

168-176: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not truncate list_bounties before applying non-newest filters/sorts.

This branch now takes the newest 100 rows first and only then runs filter_bounties_by_availability(...) and sort_bounties(...). That makes requests like sort != "newest" or availability != "all" incomplete: older matches can never appear, even when they should be in the top limit. Keep the cap only on the newest/all fast path, or push the requested sort/filter before the cap.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e44c587a-5d57-4fa5-a1e5-bd743361577c

📥 Commits

Reviewing files that changed from the base of the PR and between 75dc5e6 and 2992dec.

📒 Files selected for processing (10)
  • app/bounty_attempts.py
  • app/github_bounty_board.py
  • app/ledger/service.py
  • app/mcp.py
  • app/mcp_tools.py
  • app/models.py
  • app/serializers.py
  • app/treasury.py
  • app/treasury_executor.py
  • tests/test_mcp_board.py

Comment thread app/mcp_tools.py Outdated
Comment thread tests/test_mcp_board.py
Copy link
Copy Markdown

@tudorian95 tudorian95 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 2992dec56b156e61b73d450c76c8836ae568c5bd as a non-author.

I am requesting changes because the current head still has merge/hygiene blockers before maintainers can safely evaluate the resource behavior.

What I checked:

  • Inspected the current diff across app/mcp.py, app/mcp_tools.py, app/main.py, and tests/test_mcp_board.py.
  • Confirmed the happy-path MCP resource smoke tests now pass on this head.
  • Rechecked current-main mergeability because GitHub reports mergeStateStatus=DIRTY / mergeable=CONFLICTING.

Validation run in an isolated Docker container from this head:

  • uv run --extra dev pytest tests/test_mcp_board.py tests/test_api_mcp.py::test_mcp_tools_list_and_call -q -> 3 passed, 1 existing Starlette/httpx warning.
  • uv run --extra dev ruff check app/main.py app/mcp.py app/mcp_tools.py tests/test_mcp_board.py -> reports unsorted/unformatted import blocks in app/main.py and tests/test_mcp_board.py.
  • uv run --extra dev ruff format --check app/main.py app/mcp.py app/mcp_tools.py tests/test_mcp_board.py -> would reformat app/main.py and tests/test_mcp_board.py.
  • uv run --extra dev mypy app/mcp.py app/mcp_tools.py -> success.
  • uv run --extra dev python scripts/docs_smoke.py -> docs smoke ok.
  • git diff --check origin/main...HEAD -> reports trailing whitespace in tests/test_mcp_board.py lines 68 and 72.
  • git merge-tree --write-tree origin/main HEAD -> reports a content conflict while auto-merging app/bounty_attempts.py.

Actionable next step: rebase/merge current origin/main, resolve the app/bounty_attempts.py conflict, then run Ruff format/fix on app/main.py and tests/test_mcp_board.py and remove the trailing whitespace before rerunning the focused MCP tests.

Scope note: I did not use private data, secrets, wallet material, payout execution, treasury mutation, ledger mutation, bridge/exchange/cash-out behavior, or MRWK price behavior. This review is limited to public PR code, mergeability, and local validation evidence.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/mcp.py (1)

13-13: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Serialize dict resource payloads before writing them into text.

MCPResourceHandler on Line 13 allows handlers to return dict[str, Any], but this branch copies content into the text field unchanged. A dict-valued resource would emit {"text": {...}} instead of JSON text, which breaks the application/json response contract.

Proposed fix
         try:
             content = call_resource(database_url, uri)
+            text = content if isinstance(content, str) else json.dumps(content)
             return {
                 "jsonrpc": "2.0",
                 "id": response_id,
                 "result": {
-                    "contents": [{"uri": uri, "mimeType": "application/json", "text": content}]
+                    "contents": [{"uri": uri, "mimeType": "application/json", "text": text}]
                 },
             }

Also applies to: 169-176


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 837bc907-384c-44c5-8602-ce1db2501ee6

📥 Commits

Reviewing files that changed from the base of the PR and between 2992dec and 7e9674c.

📒 Files selected for processing (6)
  • app/bounty_attempts.py
  • app/main.py
  • app/mcp.py
  • app/mcp_tools.py
  • app/serializers.py
  • tests/test_mcp_board.py
💤 Files with no reviewable changes (1)
  • app/main.py

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/mcp.py (1)

13-13: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Serialize dict resource payloads before writing them into text.

MCPResourceHandler on Line 13 allows handlers to return dict[str, Any], but this branch copies content into the text field unchanged. A dict-valued resource would emit {"text": {...}} instead of JSON text, which breaks the application/json response contract.

Proposed fix
         try:
             content = call_resource(database_url, uri)
+            text = content if isinstance(content, str) else json.dumps(content)
             return {
                 "jsonrpc": "2.0",
                 "id": response_id,
                 "result": {
-                    "contents": [{"uri": uri, "mimeType": "application/json", "text": content}]
+                    "contents": [{"uri": uri, "mimeType": "application/json", "text": text}]
                 },
             }

Also applies to: 169-176


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 837bc907-384c-44c5-8602-ce1db2501ee6

📥 Commits

Reviewing files that changed from the base of the PR and between 2992dec and 7e9674c.

📒 Files selected for processing (6)
  • app/bounty_attempts.py
  • app/main.py
  • app/mcp.py
  • app/mcp_tools.py
  • app/serializers.py
  • tests/test_mcp_board.py
💤 Files with no reviewable changes (1)
  • app/main.py
🛑 Comments failed to post (1)
app/mcp.py (1)

24-27: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the advertised tool contract in sync with the handlers.

These descriptions say get_bounty supports issue_number/repo and list_bounty_attempts supports issue_number, but the implementations now only accept id and bounty_id respectively (see Line 177 and Line 185 in app/mcp_tools.py). MCP clients discover argument semantics from tools/list, so this will drive valid-looking calls into invalid tool arguments.

Proposed fix
     {
         "name": "get_bounty",
         "description": (
-            "Get a bounty by internal id, or by GitHub issue_number with optional repo, "
-            "optionally with accepted awards"
+            "Get a bounty by internal id, optionally with accepted awards"
         ),
     },
     {
         "name": "list_bounty_attempts",
         "description": (
-            "List advisory active-attempt reservations for a bounty by internal bounty_id, "
-            "or by GitHub issue_number with optional repo"
+            "List advisory active-attempt reservations for a bounty by internal bounty_id"
         ),
     },

Also applies to: 31-34

Copy link
Copy Markdown

@xiefuzheng713-alt xiefuzheng713-alt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on current head 7e9674cecf41c548364cf0d38ea5ba3758ae0c2a.

I found current-head blockers in the MCP tool selector contract, separate from the resource happy-path tests:

  1. get_bounty no longer accepts the advertised issue_number + optional repo selector. The existing regression tests/test_api_mcp.py::test_mcp_get_bounty_accepts_issue_number_selector now returns a JSON-RPC error instead of a result.
  2. list_bounty_attempts similarly no longer accepts issue_number + optional repo; tests/test_api_mcp.py::test_mcp_list_bounty_attempts_accepts_issue_number_selector fails with KeyError: 'result' because the response is an error.
  3. Mixed get_bounty selectors are no longer rejected. tests/test_api_mcp.py::test_mcp_get_bounty_rejects_mixed_selectors expects -32602 invalid tool arguments, but current head returns the bounty selected by id.
  4. The branch still fails formatting: uv run --extra dev ruff format --check app/main.py app/mcp.py app/mcp_tools.py tests/test_mcp_board.py reports app/mcp_tools.py would be reformatted.

Validation I ran on this exact head:

  • uv run --extra dev python -m pytest tests/test_mcp_board.py tests/test_api_mcp.py::test_mcp_tools_list_and_call tests/test_api_mcp.py::test_mcp_get_bounty_rejects_ambiguous_issue_number_selector -q -> 5 passed, 1 existing Starlette/httpx warning.
  • uv run --extra dev python -m pytest tests/test_api_mcp.py tests/test_mcp_tools.py tests/test_mcp_board.py -q -> 3 failed, 112 passed, 1 existing Starlette/httpx warning. The failed tests are the three selector regressions listed above.
  • uv run --extra dev ruff check app/main.py app/mcp.py app/mcp_tools.py tests/test_mcp_board.py -> passed.
  • uv run --extra dev ruff format --check app/main.py app/mcp.py app/mcp_tools.py tests/test_mcp_board.py -> would reformat app/mcp_tools.py.
  • uv run --extra dev mypy app/mcp.py app/mcp_tools.py -> success.
  • uv run --extra dev python scripts/docs_smoke.py -> docs smoke ok.
  • git diff --check origin/main...HEAD -> clean.
  • git merge-tree --write-tree origin/main HEAD -> clean tree 24f2149720bac7b8be23245e2d131b861dafa391.

Suggested direction: keep or restore the shared bounty selector path for get_bounty and list_bounty_attempts, preserve the mixed-selector rejection, then rerun the MCP test files and format check.

Scope note: this review used only public PR code, public GitHub check state, and local tests. I did not use private data, credentials, wallet material, payout execution, treasury mutation, ledger mutation, bridge/exchange/cash-out behavior, or MRWK price behavior.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c3a017a6-90c1-4633-bd76-1bbfec425825

📥 Commits

Reviewing files that changed from the base of the PR and between 7e9674c and 2604fa5.

📒 Files selected for processing (2)
  • app/mcp_tools.py
  • tests/test_api_mcp.py

Comment thread app/mcp_tools.py
Comment on lines +136 to +165
def find_bounty_from_args(session: Session, *, id_arg_name: str | None = None) -> Bounty | None:
has_id = (
id_arg_name is not None and id_arg_name in args and args.get(id_arg_name) is not None
)
has_issue_number = "issue_number" in args and args.get("issue_number") is not None
repo_selector = optional_repo_selector_arg()
if has_internal_id and has_issue_number:
raise ValueError(f"use {internal_id_field} or issue_number, not both")

if has_id and has_issue_number:
raise ValueError(f"use {id_arg_name} or issue_number, not both")
if repo_selector is not None and not has_issue_number:
raise ValueError("repo can only be used with issue_number")
if has_internal_id:
return session.get(Bounty, positive_int_arg(internal_id_field))

if has_id:
# id_arg_name is not None here
return session.get(Bounty, positive_int_arg(id_arg_name))

if has_issue_number:
return bounty_by_issue_number(repo_selector)
raise ValueError(f"{internal_id_field} or issue_number is required")
issue_query = select(Bounty).where(
Bounty.issue_number == positive_int_arg("issue_number")
)
if repo_selector is not None:
issue_query = issue_query.where(func.lower(Bounty.repo) == repo_selector)
bounties = session.scalars(issue_query.order_by(Bounty.id.desc()).limit(2)).all()
if not bounties:
return None
if len(bounties) > 1:
raise ValueError("issue_number matches multiple bounties")
return bounties[0]

return None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add regression tests for the new selector rules.

find_bounty_from_args() now changes lookup and validation behavior for get_bounty, list_bounty_attempts, and submit_work_proof, but the added coverage shown here only exercises resources/read. Please pin down at least the repo-without-issue_number, mixed id/issue_number, and ambiguous issue_number cases so these tool paths do not drift silently. As per coding guidelines, "Add or update tests for changed behavior".

Also applies to: 209-217, 287-298

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mrwk:needs-info More information needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants