Skip to content

Implement MCP tool registry and integration in PRReviewer#2356

Open
benny338813 wants to merge 4 commits intoThe-PR-Agent:mainfrom
benny338813:feature/mcp-integration-and-extensibility
Open

Implement MCP tool registry and integration in PRReviewer#2356
benny338813 wants to merge 4 commits intoThe-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 (3) 📘 Rule violations (7)

Grey Divider


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 📘 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 📘 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 📘 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 📘 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 📘 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 🐞 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


9. 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


10. 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:
```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


Grey Divider

Qodo Logo

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 on lines +1 to +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())
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. toolregistry missing typing imports 📘 Rule violation ≡ Correctness

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.
Agent Prompt
## 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

Comment on lines +1 to +6
import json
from typing import List, Dict, Any, Optional
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio

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

4. mcp_handler has unused imports 📘 Rule violation ⚙ Maintainability

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.
Agent Prompt
## 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

Comment on lines +212 to +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:
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

5. mcp_config accessed without validation 📘 Rule violation ☼ Reliability

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.
Agent Prompt
## 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

Comment on lines +228 to +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
)
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

6. Tool call args not guarded 📘 Rule violation ☼ Reliability

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.
Agent Prompt
## 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

get_logger().info(f"\nAI response:\n{resp}")

return resp, finish_reason
return resp, finish_reason, tool_calls
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

7. litellmaihandler now returns 3 📘 Rule violation ≡ Correctness

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.
Agent Prompt
## 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

Comment on lines 105 to 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
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

8. Unregistered commands now fail 🐞 Bug ≡ Correctness

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.
Agent Prompt
### 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

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

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