Skip to content

Implement MCP tool registry and integration in PRReviewer#2356

Open
benny338813 wants to merge 5 commits into
The-PR-Agent:mainfrom
benny338813:feature/mcp-integration-and-extensibility
Open

Implement MCP tool registry and integration in PRReviewer#2356
benny338813 wants to merge 5 commits into
The-PR-Agent:mainfrom
benny338813:feature/mcp-integration-and-extensibility

Conversation

@benny338813
Copy link
Copy Markdown

No description provided.

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Implement MCP tool registry and LLM tool calling integration

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Implement tool registry system for dynamic tool registration
• Add MCP (Model Context Protocol) integration framework
• Refactor tool instantiation to use registry pattern
• Support tool calling with LLM integration
Diagram
flowchart LR
  A["Tool Classes"] -->|register| B["ToolRegistry"]
  B -->|lookup| C["PRAgent"]
  C -->|get_tools| D["MCPHandler"]
  D -->|call_tool| E["LLM with Tools"]
  E -->|tool_calls| D
Loading

Grey Divider

File Changes

1. pr_agent/tools/registry.py ✨ Enhancement +20/-0

New tool registry for dynamic command registration

pr_agent/tools/registry.py


2. pr_agent/tools/base.py ✨ Enhancement +12/-0

Abstract base class for all PR tools

pr_agent/tools/base.py


3. pr_agent/agent/pr_agent.py Refactoring +52/-48

Refactor to use ToolRegistry instead of static mapping

pr_agent/agent/pr_agent.py


View more (7)
4. pr_agent/algo/mcp_handler.py ✨ Enhancement +41/-0

New MCP client handler for tool discovery and execution

pr_agent/algo/mcp_handler.py


5. pr_agent/algo/ai_handlers/litellm_ai_handler.py ✨ Enhancement +21/-10

Add tool calling support to chat completion method

pr_agent/algo/ai_handlers/litellm_ai_handler.py


6. pr_agent/tools/pr_reviewer.py ✨ Enhancement +55/-27

Integrate MCP tools into PR review workflow

pr_agent/tools/pr_reviewer.py


7. pr_agent/tools/pr_description.py Refactoring +27/-13

Refactor to inherit from PRTool base class

pr_agent/tools/pr_description.py


8. docs/docs/usage-guide/additional_configurations.md 📝 Documentation +27/-0

Document MCP integration and custom headers configuration

docs/docs/usage-guide/additional_configurations.md


9. utils/generate_prompt_report.py ✨ Enhancement +41/-0

New utility to generate prompt configuration report

utils/generate_prompt_report.py


10. docs/prompt_report.md 📝 Documentation +1794/-0

Generated comprehensive prompt configuration documentation

docs/prompt_report.md


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented Apr 28, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (3)

Grey Divider


Action required

1. Ruff E302: missing blank lines 📘 Rule violation ⚙ Maintainability ⭐ New
Description
New Python modules do not follow Ruff/PEP8 spacing rules (e.g., missing two blank lines after
imports before top-level definitions). This can cause Ruff CI failures and violates the repository
formatting conventions.
Code

pr_agent/tools/base.py[R1-5]

+from abc import ABC, abstractmethod
+from typing import List, Optional
+
+class PRTool(ABC):
+    def __init__(self, pr_url: str, ai_handler=None, args: Optional[List[str]] = None):
Evidence
PR Compliance ID 9 requires following the repo’s Ruff formatting conventions;
pr_agent/tools/base.py and utils/generate_prompt_report.py place top-level class/def
immediately after imports with only a single blank line (typical Ruff E302 violation).

AGENTS.md
pr_agent/tools/base.py[1-5]
utils/generate_prompt_report.py[1-6]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New Python files violate standard top-level spacing (likely Ruff E302), due to insufficient blank lines between imports and `def`/`class` definitions.

## Issue Context
The repository enforces Ruff formatting; these spacing issues can break CI/pre-commit.

## Fix Focus Areas
- pr_agent/tools/base.py[1-5]
- utils/generate_prompt_report.py[1-6]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Azure webhook ImportError 🐞 Bug ☼ Reliability ⭐ New
Description
pr_agent/servers/azuredevops_server_webhook.py imports command2class from
pr_agent.agent.pr_agent, but this PR removed that symbol (only a commented block remains). The
Azure DevOps Server webhook process will fail at import time with ImportError, breaking all ADO
webhook handling.
Code

pr_agent/agent/pr_agent.py[R11-51]

+from pr_agent.tools.registry import ToolRegistry
from pr_agent.tools.pr_reviewer import PRReviewer
-from pr_agent.tools.pr_similar_issue import PRSimilarIssue
-from pr_agent.tools.pr_update_changelog import PRUpdateChangelog
-
-command2class = {
-    "auto_review": PRReviewer,
-    "answer": PRReviewer,
-    "review": PRReviewer,
-    "review_pr": PRReviewer,
-    "describe": PRDescription,
-    "describe_pr": PRDescription,
-    "improve": PRCodeSuggestions,
-    "improve_code": PRCodeSuggestions,
-    "ask": PRQuestions,
-    "ask_question": PRQuestions,
-    "ask_line": PR_LineQuestions,
-    "update_changelog": PRUpdateChangelog,
-    "config": PRConfig,
-    "settings": PRConfig,
-    "help": PRHelpMessage,
-    "similar_issue": PRSimilarIssue,
-    "add_docs": PRAddDocs,
-    "generate_labels": PRGenerateLabels,
-    "help_docs": PRHelpDocs,
-}
-
-commands = list(command2class.keys())
+
+# Ensure all tools are imported to trigger registration
+import pr_agent.tools.pr_add_docs
+import pr_agent.tools.pr_code_suggestions
+import pr_agent.tools.pr_config
+import pr_agent.tools.pr_description
+import pr_agent.tools.pr_generate_labels
+import pr_agent.tools.pr_help_docs
+import pr_agent.tools.pr_help_message
+import pr_agent.tools.pr_line_questions
+import pr_agent.tools.pr_questions
+import pr_agent.tools.pr_similar_issue
+import pr_agent.tools.pr_update_changelog
+import pr_agent.tools.ticket_pr_compliance_check
+
+# command2class = {
+#     "auto_review": PRReviewer,
+#     "answer": PRReviewer,
+#     "review": PRReviewer,
+#     "review_pr": PRReviewer,
+#     "describe": PRDescription,
+#     "describe_pr": PRDescription,
+#     "improve": PRCodeSuggestions,
+#     "improve_code": PRCodeSuggestions,
+#     "ask": PRQuestions,
+#     "ask_question": PRQuestions,
+#     "ask_line": PR_LineQuestions,
+#     "update_changelog": PRUpdateChangelog,
+#     "config": PRConfig,
+#     "settings": PRConfig,
+#     "help": PRHelpMessage,
+#     "similar_issue": PRSimilarIssue,
+#     "add_docs": PRAddDocs,
+#     "generate_labels": PRGenerateLabels,
+#     "help_docs": PRHelpDocs,
+# }
+
+# commands = list(command2class.keys())
+commands = ToolRegistry.get_all_commands()
Evidence
command2class is no longer defined/exported in pr_agent/agent/pr_agent.py, while the Azure
DevOps Server webhook module still imports and uses it to build available_commands_rgx, causing an
import-time crash.

pr_agent/agent/pr_agent.py[11-51]
pr_agent/servers/azuredevops_server_webhook.py[22-33]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Azure DevOps Server webhook imports and uses `command2class`, but `command2class` was removed from `pr_agent.agent.pr_agent`, causing an import-time failure.

### Issue Context
The PR migrated command dispatching to `ToolRegistry`, but `azuredevops_server_webhook.py` still depends on the old mapping for both imports and command parsing.

### Fix Focus Areas
- pr_agent/servers/azuredevops_server_webhook.py[22-33]
- pr_agent/agent/pr_agent.py[11-51]

### Proposed fix
- Replace `from pr_agent.agent.pr_agent import PRAgent, command2class` with an import of `commands` (or `ToolRegistry`) and build `available_commands_rgx` from that list.
- Alternatively, re-introduce a backwards-compatible `command2class` export (as a thin adapter around `ToolRegistry`) if external integrations rely on it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Non-Lite handlers incompatible 🐞 Bug ☼ Reliability ⭐ New
Description
The PR changed tool call sites to unpack chat_completion into three values and added a tools
kwarg to the base interface, but OpenAIHandler and LangChainOpenAIHandler still return two
values and don’t accept tools. Selecting these handlers (or enabling MCP with them) will trigger
unpacking errors and/or TypeError on the unexpected kwarg.
Code

pr_agent/algo/ai_handlers/base_ai_handler.py[R19-28]

    @abstractmethod
-    async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2, img_path: str = None):
+    async def chat_completion(
+        self,
+        model: str,
+        system: str,
+        user: str,
+        temperature: float = 0.2,
+        img_path: str = None,
+        tools: Optional[List[Dict]] = None,
+    ):
Evidence
BaseAiHandler.chat_completion now includes a tools parameter and PR tools unpack three return
values, but OpenAIHandler/LangChainOpenAIHandler still define chat_completion without a
tools parameter and return only (resp, finish_reason). This makes these handlers incompatible
with the updated callers and with MCP’s tools= invocation.

pr_agent/algo/ai_handlers/base_ai_handler.py[19-28]
pr_agent/tools/pr_add_docs.py[85-97]
pr_agent/algo/ai_handlers/openai_ai_handler.py[45-64]
pr_agent/algo/ai_handlers/langchain_ai_handler.py[70-102]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The `chat_completion` contract was extended (added `tools` kwarg and a third return value for tool calls), but only `LiteLLMAIHandler` was updated. Other AI handlers now violate the interface and will break when configured.

### Issue Context
Multiple tools now assume `response, finish_reason, tool_calls = ...` (or `_`), and MCP-enabled `PRReviewer` passes `tools=...`.

### Fix Focus Areas
- pr_agent/algo/ai_handlers/base_ai_handler.py[19-28]
- pr_agent/tools/pr_add_docs.py[85-97]
- pr_agent/algo/ai_handlers/openai_ai_handler.py[45-64]
- pr_agent/algo/ai_handlers/langchain_ai_handler.py[70-102]

### Proposed fix
- Update `OpenAIHandler.chat_completion` and `LangChainOpenAIHandler.chat_completion` to accept `tools: Optional[List[Dict]] = None` (ignore or implement tool calling as appropriate).
- Update them to return a 3-tuple `(resp, finish_reason, tool_calls)` with `tool_calls=None` when not supported.
- Ensure all `BaseAiHandler` implementations follow the same signature and return shape so callers don’t need handler-specific branching.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (10)
4. Commented-out command2class block 📘 Rule violation ⚙ Maintainability
Description
A large command2class mapping is left in the code as commented-out lines, which is dead code and
adds maintenance noise. This violates the requirement to avoid commented-out code in submitted
changes.
Code

pr_agent/agent/pr_agent.py[R28-50]

+# command2class = {
+#     "auto_review": PRReviewer,
+#     "answer": PRReviewer,
+#     "review": PRReviewer,
+#     "review_pr": PRReviewer,
+#     "describe": PRDescription,
+#     "describe_pr": PRDescription,
+#     "improve": PRCodeSuggestions,
+#     "improve_code": PRCodeSuggestions,
+#     "ask": PRQuestions,
+#     "ask_question": PRQuestions,
+#     "ask_line": PR_LineQuestions,
+#     "update_changelog": PRUpdateChangelog,
+#     "config": PRConfig,
+#     "settings": PRConfig,
+#     "help": PRHelpMessage,
+#     "similar_issue": PRSimilarIssue,
+#     "add_docs": PRAddDocs,
+#     "generate_labels": PRGenerateLabels,
+#     "help_docs": PRHelpDocs,
+# }
+
+# commands = list(command2class.keys())
Evidence
PR Compliance ID 2 forbids commented-out/dead code. The PR adds an entire commented command2class
block and related commented commands line in pr_agent.py.

Rule 2: No Dead or Commented-Out Code
pr_agent/agent/pr_agent.py[28-50]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A large commented-out `command2class` block was added, which is dead code and violates the no-commented-out-code requirement.
## Issue Context
The tool dispatch was replaced by `ToolRegistry`, so this legacy mapping should be removed rather than kept commented.
## Fix Focus Areas
- pr_agent/agent/pr_agent.py[28-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Whitespace-only line has trailing spaces 📘 Rule violation ⚙ Maintainability
Description
A whitespace-only line with indentation spaces was added, which will fail pre-commit
trailing-whitespace hooks. This violates the repository’s pre-commit hygiene requirements.
Code

pr_agent/agent/pr_agent.py[110]

+            
Evidence
PR Compliance ID 10 requires no trailing whitespace in changed files. Line 110 is a blank line that
contains spaces (trailing whitespace).

AGENTS.md
pr_agent/agent/pr_agent.py[110-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A blank line containing spaces was introduced, which violates pre-commit trailing-whitespace rules.
## Issue Context
This will typically fail CI/pre-commit and should be cleaned up.
## Fix Focus Areas
- pr_agent/agent/pr_agent.py[110-110]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. ToolRegistry missing typing imports ✓ Resolved 📘 Rule violation ≡ Correctness
Description
ToolRegistry uses Optional and List in type hints without importing them, which will raise
NameError at import time and fail linting. This introduces a correctness/lint violation.
Code

pr_agent/tools/registry.py[R1-20]

+from typing import Dict, Type
+from pr_agent.tools.base import PRTool
+
+class ToolRegistry:
+    _tools: Dict[str, Type[PRTool]] = {}
+    
+    @classmethod
+    def register(cls, command: str):
+        def wrapper(subclass: Type[PRTool]):
+            cls._tools[command] = subclass
+            return subclass
+        return wrapper
+    
+    @classmethod
+    def get_tool(cls, command: str) -> Optional[Type[PRTool]]:
+        return cls._tools.get(command)
+    
+    @classmethod
+    def get_all_commands(cls) -> List[str]:
+        return list(cls._tools.keys())
Evidence
PR Compliance ID 19 requires avoiding lint/format issues; missing imports for referenced typing
symbols is a clear lint/runtime error. The file references Optional and List but imports only
Dict and Type.

pr_agent/tools/registry.py[1-20]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ToolRegistry` references `Optional` and `List` but does not import them, causing runtime import errors and failing lint checks.
## Issue Context
This is a new file introduced by the PR and should be clean under Ruff.
## Fix Focus Areas
- pr_agent/tools/registry.py[1-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. mcp_handler has unused imports ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
pr_agent/algo/mcp_handler.py imports json and asyncio but does not use them, which will be
flagged by Ruff/flake8-style unused-import checks. This violates the repository lint/tooling
compliance requirement.
Code

pr_agent/algo/mcp_handler.py[R1-6]

+import json
+from typing import List, Dict, Any, Optional
+from mcp import ClientSession, StdioServerParameters
+from mcp.client.stdio import stdio_client
+import asyncio
+
Evidence
PR Compliance ID 19 requires changes to pass linting. The new module imports json and asyncio
but neither is referenced anywhere in the file.

pr_agent/algo/mcp_handler.py[1-6]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `mcp_handler.py` file includes unused imports that will fail lint checks.
## Issue Context
Ruff typically enforces unused import removal.
## Fix Focus Areas
- pr_agent/algo/mcp_handler.py[1-6]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. mcp_config accessed without validation ✓ Resolved 📘 Rule violation ☼ Reliability
Description
mcp_config is fetched via get_settings().get("mcp", {}) and then accessed as
mcp_config.command/mcp_config.args without validating presence/types, which can raise
AttributeError at runtime for missing/incorrect config shapes. This violates the requirement to
use a consistent config access pattern and validate/normalize config values before use.
Code

pr_agent/tools/pr_reviewer.py[R212-216]

+        mcp_config = get_settings().get("mcp", {})
+        if mcp_config:
+            from pr_agent.algo.mcp_handler import MCPHandler
+            mcp_handler = MCPHandler(mcp_config.command, mcp_config.args)
+            async with mcp_handler as handler:
Evidence
PR Compliance ID 15 requires consistent, correct settings access and validation/normalization. The
code pulls mcp_config with a default {} but then assumes attribute-style access and required
fields without validation.

pr_agent/tools/pr_reviewer.py[212-216]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`mcp_config` is retrieved in a way that may yield a plain dict or an object missing required keys, but the code assumes `mcp_config.command` and `mcp_config.args` always exist.
## Issue Context
Dynaconf values can vary by layer/override; missing keys should be handled gracefully.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[212-216]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Tool call args not guarded ✓ Resolved 📘 Rule violation ☼ Reliability
Description
Tool execution assumes tool_call.function.arguments is valid JSON and that MCP tool calls always
succeed, but exceptions from json.loads(...) or handler.call_tool(...) are not handled. This can
crash the review flow instead of failing gracefully, violating robust error handling and defensive
input validation requirements.
Code

pr_agent/tools/pr_reviewer.py[R228-246]

+                if finish_reason == "tool_calls" and tool_calls:
+                    # Execute MCP tools
+                    for tool_call in tool_calls:
+                        tool_name = tool_call.function.name
+                        tool_args = json.loads(tool_call.function.arguments)
+                        get_logger().info(f"Executing tool: {tool_name} with args {tool_args}")
+                        tool_result = await handler.call_tool(tool_name, tool_args)
+                        
+                        # Add tool result to user prompt
+                        user_prompt += f"\n\nTool Result ({tool_name}): {json.dumps(tool_result)}"
+                    
+                    # Re-run completion with tool results
+                    response, finish_reason, _ = await self.ai_handler.chat_completion(
+                        model=model,
+                        temperature=get_settings().config.temperature,
+                        system=system_prompt,
+                        user=user_prompt,
+                        tools=None # Don't allow recursive tool calls for now
+                    )
Evidence
PR Compliance ID 3 requires handling error scenarios gracefully, and PR Compliance ID 16 requires
validating external inputs before method calls/parsing. The code parses tool-call arguments from the
LLM with json.loads(...) and executes external tool calls without any try/except or validation.

Rule 3: Robust Error Handling
pr_agent/tools/pr_reviewer.py[228-246]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Tool calling can raise exceptions (invalid JSON arguments, MCP server errors) and currently crashes the review path.
## Issue Context
Tool calls are driven by model output (external/untrusted shape) and an external MCP server; both require defensive handling.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[228-246]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. LiteLLMAIHandler now returns 3 ✓ Resolved 📘 Rule violation ≡ Correctness
Description
LiteLLMAIHandler.chat_completion() now returns three values (resp, finish_reason,
tool_calls), but multiple call sites still unpack only two values, which will raise `ValueError:
too many values to unpack` at runtime. This is a robustness issue that can break normal tool
execution flows.
Code

pr_agent/algo/ai_handlers/litellm_ai_handler.py[442]

+        return resp, finish_reason, tool_calls
Evidence
PR Compliance ID 3 requires preventing unhandled exceptions in normal execution. The modified
handler returns three values, while existing tool code (e.g., PRAddDocs) still expects two return
values from chat_completion(), causing an immediate runtime exception.

Rule 3: Robust Error Handling
pr_agent/algo/ai_handlers/litellm_ai_handler.py[442-442]
pr_agent/tools/pr_add_docs.py[92-93]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`LiteLLMAIHandler.chat_completion()` now returns a 3-tuple, but many callers unpack a 2-tuple, causing runtime failures.
## Issue Context
Either keep backward compatibility (e.g., return 2 values unless tool-calling is enabled / provide a separate method), or update all call sites (and the `BaseAiHandler` interface if needed) to unpack the third return value.
## Fix Focus Areas
- pr_agent/algo/ai_handlers/litellm_ai_handler.py[270-272]
- pr_agent/algo/ai_handlers/litellm_ai_handler.py[420-442]
- pr_agent/tools/pr_add_docs.py[92-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Unregistered commands now fail ✓ Resolved 🐞 Bug ≡ Correctness
Description
PRAgent now dispatches exclusively via ToolRegistry, but only PRReviewer/PRDescription are
registered; commands like improve, ask, add_docs, etc. will be treated as unknown and the CLI
will reject them because choices=commands is derived from the registry.
Code

pr_agent/agent/pr_agent.py[R105-123]

      action = action.lstrip("/").lower()
-        if action not in command2class:
+        tool_class = ToolRegistry.get_tool(action)
+        if not tool_class:
          get_logger().warning(f"Unknown command: {action}")
          return False
+            
      with get_logger().contextualize(command=action, pr_url=pr_url):
          get_logger().info("PR-Agent request handler started", analytics=True)
-            if action == "answer":
-                if notify:
-                    notify()
-                await PRReviewer(pr_url, is_answer=True, args=args, ai_handler=self.ai_handler).run()
-            elif action == "auto_review":
-                await PRReviewer(pr_url, is_auto=True, args=args, ai_handler=self.ai_handler).run()
-            elif action in command2class:
-                if notify:
-                    notify()
-
-                await command2class[action](pr_url, ai_handler=self.ai_handler, args=args).run()
+            
+            if notify:
+                notify()
+
+            if tool_class == PRReviewer:
+                is_answer = (action == "answer")
+                is_auto = (action == "auto_review")
+                await PRReviewer(pr_url, is_answer=is_answer, is_auto=is_auto, args=args, ai_handler=self.ai_handler).run()
          else:
-                return False
+                await tool_class(pr_url, ai_handler=self.ai_handler, args=args).run()
          return True
Evidence
Dispatch is now ToolRegistry.get_tool(action) and commands is sourced from
ToolRegistry.get_all_commands(). Only PRReviewer/PRDescription show registrations, while other
tools (e.g., PRCodeSuggestions, PRAddDocs) lack registration decorators and therefore won’t be
discoverable. The CLI enforces the registry-derived list via argparse choices=commands, so these
commands can’t be invoked.

pr_agent/agent/pr_agent.py[14-52]
pr_agent/agent/pr_agent.py[105-123]
pr_agent/tools/pr_reviewer.py[34-38]
pr_agent/tools/pr_description.py[36-38]
pr_agent/tools/pr_code_suggestions.py[33-36]
pr_agent/tools/pr_add_docs.py[18-22]
pr_agent/cli.py[5-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Command dispatch now depends on `ToolRegistry`, but most tools are not registered, so many commands become unusable and CLI `choices` excludes them.
### Issue Context
Previously, `command2class` provided coverage for many commands. After the migration, each tool must self-register (or a compatibility mapping must remain).
### Fix Focus Areas
- pr_agent/agent/pr_agent.py[14-52]
- pr_agent/agent/pr_agent.py[105-123]
- pr_agent/cli.py[5-56]
- pr_agent/tools/pr_code_suggestions.py[33-60]
- pr_agent/tools/pr_add_docs.py[18-40]
- pr_agent/tools/pr_questions.py[18-60]
- pr_agent/tools/pr_config.py[8-31]
### Expected change
Add `@ToolRegistry.register(...)` decorators for every supported command (e.g., `improve`, `improve_code`, `ask`, `ask_question`, `add_docs`, `update_changelog`, `help_docs`, `generate_labels`, `config/settings`, `help`, `similar_issue`, etc.), or reintroduce a backward-compatible `command2class`/fallback dispatch while migrating.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. PRDescription init crashes 🐞 Bug ≡ Correctness
Description
PRDescription.__init__ now duplicates initialization logic and references self.main_pr_language
even though it is no longer set, causing AttributeError during tool construction.
Code

pr_agent/tools/pr_description.py[R39-64]

+    def __init__(self, pr_url: str, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler, args: list = None):
+        super().__init__(pr_url, ai_handler=ai_handler, args=args)
      self.git_provider = get_git_provider_with_context(pr_url)
-        self.main_pr_language = get_main_pr_language(
-            self.git_provider.get_languages(), self.git_provider.get_files()
+        self.ai_handler = ai_handler()
+        self.vars = {
+            "title": self.git_provider.pr.title,
+            "branch": self.git_provider.get_pr_branch(),
+            "description": self.git_provider.get_pr_description(),
+            "language": get_main_pr_language(
+                self.git_provider.get_languages(), self.git_provider.get_files()
+            ),
+            "commit_messages_str": self.git_provider.get_commit_messages(),
+            "extra_instructions": get_settings().pr_description_prompt.extra_instructions,
+            'duplicate_prompt_examples': get_settings().config.get('duplicate_prompt_examples', False),
+            "enable_custom_labels": get_settings().config.enable_custom_labels,
+        }
+        self.token_handler = TokenHandler(
+            self.git_provider.pr,
+            self.vars,
+            get_settings().pr_description_prompt.system,
+            get_settings().pr_description_prompt.user
      )
+
      self.pr_id = self.git_provider.get_pr_id()
      self.keys_fix = ["filename:", "language:", "changes_summary:", "changes_title:", "description:", "title:"]
Evidence
The new constructor block builds vars with a computed language but doesn’t assign
self.main_pr_language. Later in the same __init__, it sets `self.ai_handler.main_pr_language =
self.main_pr_language and rebuilds self.vars/TokenHandler` again, which will fail because
self.main_pr_language is undefined and the duplicated initialization is internally inconsistent.

pr_agent/tools/pr_description.py[39-103]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PRDescription.__init__` contains two initialization blocks and uses `self.main_pr_language` without defining it, causing an `AttributeError` and inconsistent object state.
### Issue Context
This appears to be a partial refactor to `PRTool`/registry, leaving old initialization in place.
### Fix Focus Areas
- pr_agent/tools/pr_description.py[39-103]
### Expected change
Remove the duplicated initialization and ensure `self.main_pr_language` (or equivalent) is set exactly once before being used (and align `self.vars` / `TokenHandler` creation to that single source of truth).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. MCP tool_calls parsing crash 🐞 Bug ≡ Correctness
Description
When MCP tools are enabled, PRReviewer assumes each tool_call has .function.name/.arguments,
but the LiteLLM response passes tool_calls as dicts, so tool execution will crash before any MCP
tool can run.
Code

pr_agent/tools/pr_reviewer.py[R228-238]

+                if finish_reason == "tool_calls" and tool_calls:
+                    # Execute MCP tools
+                    for tool_call in tool_calls:
+                        tool_name = tool_call.function.name
+                        tool_args = json.loads(tool_call.function.arguments)
+                        get_logger().info(f"Executing tool: {tool_name} with args {tool_args}")
+                        tool_result = await handler.call_tool(tool_name, tool_args)
+                        
+                        # Add tool result to user prompt
+                        user_prompt += f"\n\nTool Result ({tool_name}): {json.dumps(tool_result)}"
+                    
Evidence
LiteLLMAIHandler extracts message = response["choices"][0]["message"] as a dict and returns
tool_calls = message.get('tool_calls') (also dict-shaped). PRReviewer then accesses
tool_call.function.name which will raise AttributeError for dict entries.

pr_agent/tools/pr_reviewer.py[212-246]
pr_agent/algo/ai_handlers/litellm_ai_handler.py[458-470]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PRReviewer` parses `tool_calls` using attribute access (`tool_call.function.name`), but the handler returns tool calls as dictionaries from the OpenAI/LiteLLM response.
### Issue Context
Tool calls are typically shaped like:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit 463e639

Results up to commit N/A


🐞 Bugs (2) 📘 Rule violations (2) 📎 Requirement gaps (0)


Action required
1. Commented-out command2class block 📘 Rule violation ⚙ Maintainability
Description
A large command2class mapping is left in the code as commented-out lines, which is dead code and
adds maintenance noise. This violates the requirement to avoid commented-out code in submitted
changes.
Code

pr_agent/agent/pr_agent.py[R28-50]

+# command2class = {
+#     "auto_review": PRReviewer,
+#     "answer": PRReviewer,
+#     "review": PRReviewer,
+#     "review_pr": PRReviewer,
+#     "describe": PRDescription,
+#     "describe_pr": PRDescription,
+#     "improve": PRCodeSuggestions,
+#     "improve_code": PRCodeSuggestions,
+#     "ask": PRQuestions,
+#     "ask_question": PRQuestions,
+#     "ask_line": PR_LineQuestions,
+#     "update_changelog": PRUpdateChangelog,
+#     "config": PRConfig,
+#     "settings": PRConfig,
+#     "help": PRHelpMessage,
+#     "similar_issue": PRSimilarIssue,
+#     "add_docs": PRAddDocs,
+#     "generate_labels": PRGenerateLabels,
+#     "help_docs": PRHelpDocs,
+# }
+
+# commands = list(command2class.keys())
Evidence
PR Compliance ID 2 forbids commented-out/dead code. The PR adds an entire commented command2class
block and related commented commands line in pr_agent.py.

Rule 2: No Dead or Commented-Out Code
pr_agent/agent/pr_agent.py[28-50]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A large commented-out `command2class` block was added, which is dead code and violates the no-commented-out-code requirement.
## Issue Context
The tool dispatch was replaced by `ToolRegistry`, so this legacy mapping should be removed rather than kept commented.
## Fix Focus Areas
- pr_agent/agent/pr_agent.py[28-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Whitespace-only line has trailing spaces 📘 Rule violation ⚙ Maintainability
Description
A whitespace-only line with indentation spaces was added, which will fail pre-commit
trailing-whitespace hooks. This violates the repository’s pre-commit hygiene requirements.
Code

pr_agent/agent/pr_agent.py[110]

+            
Evidence
PR Compliance ID 10 requires no trailing whitespace in changed files. Line 110 is a blank line that
contains spaces (trailing whitespace).

AGENTS.md
pr_agent/agent/pr_agent.py[110-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A blank line containing spaces was introduced, which violates pre-commit trailing-whitespace rules.
## Issue Context
This will typically fail CI/pre-commit and should be cleaned up.
## Fix Focus Areas
- pr_agent/agent/pr_agent.py[110-110]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. ToolRegistry missing typing imports ✓ Resolved 📘 Rule violation ≡ Correctness
Description
ToolRegistry uses Optional and List in type hints without importing them, which will raise
NameError at import time and fail linting. This introduces a correctness/lint violation.
Code

pr_agent/tools/registry.py[R1-20]

+from typing import Dict, Type
+from pr_agent.tools.base import PRTool
+
+class ToolRegistry:
+    _tools: Dict[str, Type[PRTool]] = {}
+    
+    @classmethod
+    def register(cls, command: str):
+        def wrapper(subclass: Type[PRTool]):
+            cls._tools[command] = subclass
+            return subclass
+        return wrapper
+    
+    @classmethod
+    def get_tool(cls, command: str) -> Optional[Type[PRTool]]:
+        return cls._tools.get(command)
+    
+    @classmethod
+    def get_all_commands(cls) -> List[str]:
+        return list(cls._tools.keys())
Evidence
PR Compliance ID 19 requires avoiding lint/format issues; missing imports for referenced typing
symbols is a clear lint/runtime error. The file references Optional and List but imports only
Dict and Type.

pr_agent/tools/registry.py[1-20]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ToolRegistry` references `Optional` and `List` but does not import them, causing runtime import errors and failing lint checks.
## Issue Context
This is a new file introduced by the PR and should be clean under Ruff.
## Fix Focus Areas
- pr_agent/tools/registry.py[1-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (7)
4. mcp_handler has unused imports ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
pr_agent/algo/mcp_handler.py imports json and asyncio but does not use them, which will be
flagged by Ruff/flake8-style unused-import checks. This violates the repository lint/tooling
compliance requirement.
Code

pr_agent/algo/mcp_handler.py[R1-6]

+import json
+from typing import List, Dict, Any, Optional
+from mcp import ClientSession, StdioServerParameters
+from mcp.client.stdio import stdio_client
+import asyncio
+
Evidence
PR Compliance ID 19 requires changes to pass linting. The new module imports json and asyncio
but neither is referenced anywhere in the file.

pr_agent/algo/mcp_handler.py[1-6]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `mcp_handler.py` file includes unused imports that will fail lint checks.
## Issue Context
Ruff typically enforces unused import removal.
## Fix Focus Areas
- pr_agent/algo/mcp_handler.py[1-6]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. mcp_config accessed without validation ✓ Resolved 📘 Rule violation ☼ Reliability
Description
mcp_config is fetched via get_settings().get("mcp", {}) and then accessed as
mcp_config.command/mcp_config.args without validating presence/types, which can raise
AttributeError at runtime for missing/incorrect config shapes. This violates the requirement to
use a consistent config access pattern and validate/normalize config values before use.
Code

pr_agent/tools/pr_reviewer.py[R212-216]

+        mcp_config = get_settings().get("mcp", {})
+        if mcp_config:
+            from pr_agent.algo.mcp_handler import MCPHandler
+            mcp_handler = MCPHandler(mcp_config.command, mcp_config.args)
+            async with mcp_handler as handler:
Evidence
PR Compliance ID 15 requires consistent, correct settings access and validation/normalization. The
code pulls mcp_config with a default {} but then assumes attribute-style access and required
fields without validation.

pr_agent/tools/pr_reviewer.py[212-216]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`mcp_config` is retrieved in a way that may yield a plain dict or an object missing required keys, but the code assumes `mcp_config.command` and `mcp_config.args` always exist.
## Issue Context
Dynaconf values can vary by layer/override; missing keys should be handled gracefully.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[212-216]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Tool call args not guarded ✓ Resolved 📘 Rule violation ☼ Reliability
Description
Tool execution assumes tool_call.function.arguments is valid JSON and that MCP tool calls always
succeed, but exceptions from json.loads(...) or handler.call_tool(...) are not handled. This can
crash the review flow instead of failing gracefully, violating robust error handling and defensive
input validation requirements.
Code

pr_agent/tools/pr_reviewer.py[R228-246]

+                if finish_reason == "tool_calls" and tool_calls:
+                    # Execute MCP tools
+                    for tool_call in tool_calls:
+                        tool_name = tool_call.function.name
+                        tool_args = json.loads(tool_call.function.arguments)
+                        get_logger().info(f"Executing tool: {tool_name} with args {tool_args}")
+                        tool_result = await handler.call_tool(tool_name, tool_args)
+                        
+                        # Add tool result to user prompt
+                        user_prompt += f"\n\nTool Result ({tool_name}): {json.dumps(tool_result)}"
+                    
+                    # Re-run completion with tool results
+                    response, finish_reason, _ = await self.ai_handler.chat_completion(
+                        model=model,
+                        temperature=get_settings().config.temperature,
+                        system=system_prompt,
+                        user=user_prompt,
+                        tools=None # Don't allow recursive tool calls for now
+                    )
Evidence
PR Compliance ID 3 requires handling error scenarios gracefully, and PR Compliance ID 16 requires
validating external inputs before method calls/parsing. The code parses tool-call arguments from the
LLM with json.loads(...) and executes external tool calls without any try/except or validation.

Rule 3: Robust Error Handling
pr_agent/tools/pr_reviewer.py[228-246]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Tool calling can raise exceptions (invalid JSON arguments, MCP server errors) and currently crashes the review path.
## Issue Context
Tool calls are driven by model output (external/untrusted shape) and an external MCP server; both require defensive handling.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[228-246]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. LiteLLMAIHandler now returns 3 ✓ Resolved 📘 Rule violation ≡ Correctness
Description
LiteLLMAIHandler.chat_completion() now returns three values (resp, finish_reason,
tool_calls), but multiple call sites still unpack only two values, which will raise `ValueError:
too many values to unpack` at runtime. This is a robustness issue that can break normal tool
execution flows.
Code

pr_agent/algo/ai_handlers/litellm_ai_handler.py[442]

+        return resp, finish_reason, tool_calls
Evidence
PR Compliance ID 3 requires preventing unhandled exceptions in normal execution. The modified
handler returns three values, while existing tool code (e.g., PRAddDocs) still expects two return
values from chat_completion(), causing an immediate runtime exception.

Rule 3: Robust Error Handling
pr_agent/algo/ai_handlers/litellm_ai_handler.py[442-442]
pr_agent/tools/pr_add_docs.py[92-93]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`LiteLLMAIHandler.chat_completion()` now returns a 3-tuple, but many callers unpack a 2-tuple, causing runtime failures.
## Issue Context
Either keep backward compatibility (e.g., return 2 values unless tool-calling is enabled / provide a separate method), or update all call sites (and the `BaseAiHandler` interface if needed) to unpack the third return value.
## Fix Focus Areas
- pr_agent/algo/ai_handlers/litellm_ai_handler.py[270-272]
- pr_agent/algo/ai_handlers/litellm_ai_handler.py[420-442]
- pr_agent/tools/pr_add_docs.py[92-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Unregistered commands now fail ✓ Resolved 🐞 Bug ≡ Correctness
Description
PRAgent now dispatches exclusively via ToolRegistry, but only PRReviewer/PRDescription are
registered; commands like improve, ask, add_docs, etc. will be treated as unknown and the CLI
will reject them because choices=commands is derived from the registry.
Code

pr_agent/agent/pr_agent.py[R105-123]

       action = action.lstrip("/").lower()
-        if action not in command2class:
+        tool_class = ToolRegistry.get_tool(action)
+        if not tool_class:
           get_logger().warning(f"Unknown command: {action}")
           return False
+            
       with get_logger().contextualize(command=action, pr_url=pr_url):
           get_logger().info("PR-Agent request handler started", analytics=True)
-            if action == "answer":
-                if notify:
-                    notify()
-                await PRReviewer(pr_url, is_answer=True, args=args, ai_handler=self.ai_handler).run()
-            elif action == "auto_review":
-                await PRReviewer(pr_url, is_auto=True, args=args, ai_handler=self.ai_handler).run()
-            elif action in command2class:
-                if notify:
-                    notify()
-
-                await command2class[action](pr_url, ai_handler=self.ai_handler, args=args).run()
+            
+            if notify:
+                notify()
+
+            if tool_class == PRReviewer:
+                is_answer = (action == "answer")
+                is_auto = (action == "auto_review")
+                await PRReviewer(pr_url, is_answer=is_answer, is_auto=is_auto, args=args, ai_handler=self.ai_handler).run()
           else:
-                return False
+                await tool_class(pr_url, ai_handler=self.ai_handler, args=args).run()
           return True
Evidence
Dispatch is now ToolRegistry.get_tool(action) and commands is sourced from
ToolRegistry.get_all_commands(). Only PRReviewer/PRDescription show registrations, while other
tools (e.g., PRCodeSuggestions, PRAddDocs) lack registration decorators and therefore won’t be
discoverable. The CLI enforces the registry-derived list via argparse choices=commands, so these
commands can’t be invoked.

pr_agent/agent/pr_agent.py[14-52]
pr_agent/agent/pr_agent.py[105-123]
pr_agent/tools/pr_reviewer.py[34-38]
pr_agent/tools/pr_description.py[36-38]
pr_agent/tools/pr_code_suggestions.py[33-36]
pr_agent/tools/pr_add_docs.py[18-22]
pr_agent/cli.py[5-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Command dispatch now depends on `ToolRegistry`, but most tools are not registered, so many commands become unusable and CLI `choices` excludes them.
### Issue Context
Previously, `command2class` provided coverage for many commands. After the migration, each tool must self-register (or a compatibility mapping must remain).
### Fix Focus Areas
- pr_agent/agent/pr_agent.py[14-52]
- pr_agent/agent/pr_agent.py[105-123]
- pr_agent/cli.py[5-56]
- pr_agent/tools/pr_code_suggestions.py[33-60]
- pr_agent/tools/pr_add_docs.py[18-40]
- pr_agent/tools/pr_questions.py[18-60]
- pr_agent/tools/pr_config.py[8-31]
### Expected change
Add `@ToolRegistry.register(...)` decorators for every supported command (e.g., `improve`, `improve_code`, `ask`, `ask_question`, `add_docs`, `update_changelog`, `help_docs`,...

Comment on lines +28 to +50
# command2class = {
# "auto_review": PRReviewer,
# "answer": PRReviewer,
# "review": PRReviewer,
# "review_pr": PRReviewer,
# "describe": PRDescription,
# "describe_pr": PRDescription,
# "improve": PRCodeSuggestions,
# "improve_code": PRCodeSuggestions,
# "ask": PRQuestions,
# "ask_question": PRQuestions,
# "ask_line": PR_LineQuestions,
# "update_changelog": PRUpdateChangelog,
# "config": PRConfig,
# "settings": PRConfig,
# "help": PRHelpMessage,
# "similar_issue": PRSimilarIssue,
# "add_docs": PRAddDocs,
# "generate_labels": PRGenerateLabels,
# "help_docs": PRHelpDocs,
# }

# commands = list(command2class.keys())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. Commented-out command2class block 📘 Rule violation ⚙ Maintainability

A large command2class mapping is left in the code as commented-out lines, which is dead code and
adds maintenance noise. This violates the requirement to avoid commented-out code in submitted
changes.
Agent Prompt
## Issue description
A large commented-out `command2class` block was added, which is dead code and violates the no-commented-out-code requirement.

## Issue Context
The tool dispatch was replaced by `ToolRegistry`, so this legacy mapping should be removed rather than kept commented.

## Fix Focus Areas
- pr_agent/agent/pr_agent.py[28-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

if not tool_class:
get_logger().warning(f"Unknown command: {action}")
return False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

2. Whitespace-only line has trailing spaces 📘 Rule violation ⚙ Maintainability

A whitespace-only line with indentation spaces was added, which will fail pre-commit
trailing-whitespace hooks. This violates the repository’s pre-commit hygiene requirements.
Agent Prompt
## Issue description
A blank line containing spaces was introduced, which violates pre-commit trailing-whitespace rules.

## Issue Context
This will typically fail CI/pre-commit and should be cleaned up.

## Fix Focus Areas
- pr_agent/agent/pr_agent.py[110-110]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread pr_agent/tools/registry.py Outdated
Comment thread pr_agent/algo/mcp_handler.py Outdated
Comment thread pr_agent/tools/pr_reviewer.py Outdated
Comment thread pr_agent/tools/pr_reviewer.py
Comment thread pr_agent/algo/ai_handlers/litellm_ai_handler.py
Comment thread pr_agent/agent/pr_agent.py
Comment on lines +39 to 64
def __init__(self, pr_url: str, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler, args: list = None):
super().__init__(pr_url, ai_handler=ai_handler, args=args)
self.git_provider = get_git_provider_with_context(pr_url)
self.main_pr_language = get_main_pr_language(
self.git_provider.get_languages(), self.git_provider.get_files()
self.ai_handler = ai_handler()
self.vars = {
"title": self.git_provider.pr.title,
"branch": self.git_provider.get_pr_branch(),
"description": self.git_provider.get_pr_description(),
"language": get_main_pr_language(
self.git_provider.get_languages(), self.git_provider.get_files()
),
"commit_messages_str": self.git_provider.get_commit_messages(),
"extra_instructions": get_settings().pr_description_prompt.extra_instructions,
'duplicate_prompt_examples': get_settings().config.get('duplicate_prompt_examples', False),
"enable_custom_labels": get_settings().config.enable_custom_labels,
}
self.token_handler = TokenHandler(
self.git_provider.pr,
self.vars,
get_settings().pr_description_prompt.system,
get_settings().pr_description_prompt.user
)

self.pr_id = self.git_provider.get_pr_id()
self.keys_fix = ["filename:", "language:", "changes_summary:", "changes_title:", "description:", "title:"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

9. Prdescription init crashes 🐞 Bug ≡ Correctness

PRDescription.__init__ now duplicates initialization logic and references self.main_pr_language
even though it is no longer set, causing AttributeError during tool construction.
Agent Prompt
### Issue description
`PRDescription.__init__` contains two initialization blocks and uses `self.main_pr_language` without defining it, causing an `AttributeError` and inconsistent object state.

### Issue Context
This appears to be a partial refactor to `PRTool`/registry, leaving old initialization in place.

### Fix Focus Areas
- pr_agent/tools/pr_description.py[39-103]

### Expected change
Remove the duplicated initialization and ensure `self.main_pr_language` (or equivalent) is set exactly once before being used (and align `self.vars` / `TokenHandler` creation to that single source of truth).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +228 to +238
if finish_reason == "tool_calls" and tool_calls:
# Execute MCP tools
for tool_call in tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
get_logger().info(f"Executing tool: {tool_name} with args {tool_args}")
tool_result = await handler.call_tool(tool_name, tool_args)

# Add tool result to user prompt
user_prompt += f"\n\nTool Result ({tool_name}): {json.dumps(tool_result)}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

10. Mcp tool_calls parsing crash 🐞 Bug ≡ Correctness

When MCP tools are enabled, PRReviewer assumes each tool_call has .function.name/.arguments,
but the LiteLLM response passes tool_calls as dicts, so tool execution will crash before any MCP
tool can run.
Agent Prompt
### Issue description
`PRReviewer` parses `tool_calls` using attribute access (`tool_call.function.name`), but the handler returns tool calls as dictionaries from the OpenAI/LiteLLM response.

### Issue Context
Tool calls are typically shaped like:
```json
{"id": "...", "type": "function", "function": {"name": "...", "arguments": "{...}"}}
```

### Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[228-238]
- pr_agent/algo/ai_handlers/litellm_ai_handler.py[458-470]

### Expected change
Update parsing to support dict tool calls (e.g., `tool_call["function"]["name"]` and `tool_call["function"]["arguments"]`), and add defensive handling for missing/invalid JSON arguments so MCP execution can proceed or fail gracefully.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented May 10, 2026

Persistent review updated to latest commit 463e639

Comment thread pr_agent/tools/base.py
Comment on lines +1 to +5
from abc import ABC, abstractmethod
from typing import List, Optional

class PRTool(ABC):
def __init__(self, pr_url: str, ai_handler=None, args: Optional[List[str]] = None):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. Ruff e302: missing blank lines 📘 Rule violation ⚙ Maintainability

New Python modules do not follow Ruff/PEP8 spacing rules (e.g., missing two blank lines after
imports before top-level definitions). This can cause Ruff CI failures and violates the repository
formatting conventions.
Agent Prompt
## Issue description
New Python files violate standard top-level spacing (likely Ruff E302), due to insufficient blank lines between imports and `def`/`class` definitions.

## Issue Context
The repository enforces Ruff formatting; these spacing issues can break CI/pre-commit.

## Fix Focus Areas
- pr_agent/tools/base.py[1-5]
- utils/generate_prompt_report.py[1-6]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +11 to +51
from pr_agent.tools.registry import ToolRegistry
from pr_agent.tools.pr_reviewer import PRReviewer
from pr_agent.tools.pr_similar_issue import PRSimilarIssue
from pr_agent.tools.pr_update_changelog import PRUpdateChangelog

command2class = {
"auto_review": PRReviewer,
"answer": PRReviewer,
"review": PRReviewer,
"review_pr": PRReviewer,
"describe": PRDescription,
"describe_pr": PRDescription,
"improve": PRCodeSuggestions,
"improve_code": PRCodeSuggestions,
"ask": PRQuestions,
"ask_question": PRQuestions,
"ask_line": PR_LineQuestions,
"update_changelog": PRUpdateChangelog,
"config": PRConfig,
"settings": PRConfig,
"help": PRHelpMessage,
"similar_issue": PRSimilarIssue,
"add_docs": PRAddDocs,
"generate_labels": PRGenerateLabels,
"help_docs": PRHelpDocs,
}

commands = list(command2class.keys())

# Ensure all tools are imported to trigger registration
import pr_agent.tools.pr_add_docs
import pr_agent.tools.pr_code_suggestions
import pr_agent.tools.pr_config
import pr_agent.tools.pr_description
import pr_agent.tools.pr_generate_labels
import pr_agent.tools.pr_help_docs
import pr_agent.tools.pr_help_message
import pr_agent.tools.pr_line_questions
import pr_agent.tools.pr_questions
import pr_agent.tools.pr_similar_issue
import pr_agent.tools.pr_update_changelog
import pr_agent.tools.ticket_pr_compliance_check

# command2class = {
# "auto_review": PRReviewer,
# "answer": PRReviewer,
# "review": PRReviewer,
# "review_pr": PRReviewer,
# "describe": PRDescription,
# "describe_pr": PRDescription,
# "improve": PRCodeSuggestions,
# "improve_code": PRCodeSuggestions,
# "ask": PRQuestions,
# "ask_question": PRQuestions,
# "ask_line": PR_LineQuestions,
# "update_changelog": PRUpdateChangelog,
# "config": PRConfig,
# "settings": PRConfig,
# "help": PRHelpMessage,
# "similar_issue": PRSimilarIssue,
# "add_docs": PRAddDocs,
# "generate_labels": PRGenerateLabels,
# "help_docs": PRHelpDocs,
# }

# commands = list(command2class.keys())
commands = ToolRegistry.get_all_commands()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

2. Azure webhook importerror 🐞 Bug ☼ Reliability

pr_agent/servers/azuredevops_server_webhook.py imports command2class from
pr_agent.agent.pr_agent, but this PR removed that symbol (only a commented block remains). The
Azure DevOps Server webhook process will fail at import time with ImportError, breaking all ADO
webhook handling.
Agent Prompt
### Issue description
Azure DevOps Server webhook imports and uses `command2class`, but `command2class` was removed from `pr_agent.agent.pr_agent`, causing an import-time failure.

### Issue Context
The PR migrated command dispatching to `ToolRegistry`, but `azuredevops_server_webhook.py` still depends on the old mapping for both imports and command parsing.

### Fix Focus Areas
- pr_agent/servers/azuredevops_server_webhook.py[22-33]
- pr_agent/agent/pr_agent.py[11-51]

### Proposed fix
- Replace `from pr_agent.agent.pr_agent import PRAgent, command2class` with an import of `commands` (or `ToolRegistry`) and build `available_commands_rgx` from that list.
- Alternatively, re-introduce a backwards-compatible `command2class` export (as a thin adapter around `ToolRegistry`) if external integrations rely on it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 19 to +28
@abstractmethod
async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2, img_path: str = None):
async def chat_completion(
self,
model: str,
system: str,
user: str,
temperature: float = 0.2,
img_path: str = None,
tools: Optional[List[Dict]] = None,
):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

3. Non-lite handlers incompatible 🐞 Bug ☼ Reliability

The PR changed tool call sites to unpack chat_completion into three values and added a tools
kwarg to the base interface, but OpenAIHandler and LangChainOpenAIHandler still return two
values and don’t accept tools. Selecting these handlers (or enabling MCP with them) will trigger
unpacking errors and/or TypeError on the unexpected kwarg.
Agent Prompt
### Issue description
The `chat_completion` contract was extended (added `tools` kwarg and a third return value for tool calls), but only `LiteLLMAIHandler` was updated. Other AI handlers now violate the interface and will break when configured.

### Issue Context
Multiple tools now assume `response, finish_reason, tool_calls = ...` (or `_`), and MCP-enabled `PRReviewer` passes `tools=...`.

### Fix Focus Areas
- pr_agent/algo/ai_handlers/base_ai_handler.py[19-28]
- pr_agent/tools/pr_add_docs.py[85-97]
- pr_agent/algo/ai_handlers/openai_ai_handler.py[45-64]
- pr_agent/algo/ai_handlers/langchain_ai_handler.py[70-102]

### Proposed fix
- Update `OpenAIHandler.chat_completion` and `LangChainOpenAIHandler.chat_completion` to accept `tools: Optional[List[Dict]] = None` (ignore or implement tool calling as appropriate).
- Update them to return a 3-tuple `(resp, finish_reason, tool_calls)` with `tool_calls=None` when not supported.
- Ensure all `BaseAiHandler` implementations follow the same signature and return shape so callers don’t need handler-specific branching.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant