-
Couldn't load subscription status.
- Fork 1
feat: add repair command for MCP before retrying #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Reviewer's GuideThis PR extends MCPClient with a per-server repair command: on MCP errors during tool retrieval, it runs a configured shell command and retries; to support this it adds a new config field and refactors error handling and filtering into helpers. Sequence diagram for MCPClient tool retrieval with repair commandsequenceDiagram
participant MCPClient
participant Server
participant Shell
participant Logger
MCPClient->>Server: get_tools(server_name)
Note over Server: MCP error occurs
Server-->>MCPClient: Exception
MCPClient->>Shell: run repair_command (if configured)
Shell-->>MCPClient: repair command completes
MCPClient->>Server: get_tools(server_name) (retry)
Server-->>MCPClient: tools or Exception
alt success
MCPClient->>MCPClient: filter_tools()
else failure
MCPClient->>Logger: log error
end
Class diagram for updated MCPServerConfig and MCPClientclassDiagram
class MCPServerConfig {
+list[str] include
+list[str] exclude
+bool enabled
+str|None repair_command
}
class MCPClient {
+dict[str, Connection] connections
+dict[str, dict] tool_filters
+dict[str, str] repair_commands
+bool enable_approval
+list[BaseTool]|None _tools_cache
+dict[str, str] _module_map
+asyncio.Lock _init_lock
+_is_mcp_error(exc: Exception) bool
+_filter_tools(tools: list[BaseTool], server_name: str) list[BaseTool]
+_get_server_tools(server_name: str) async list[BaseTool]
+get_mcp_tools() async list[BaseTool]
}
MCPClient --> MCPServerConfig : uses
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
Blocking issues:
- Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
- Found 'subprocess' function 'run' with 'shell=True'. This is dangerous because this call will spawn the command using a shell process. Doing so propagates current shell settings and variables, which makes it much easier for a malicious actor to execute commands. Use 'shell=False' instead. (link)
General comments:
- Running subprocess.run in an async method will block the event loop—consider using asyncio.create_subprocess_exec or run the repair command in an executor to avoid blocking.
- Wrap the repair subprocess invocation in its own try/except (and avoid shell=True if possible) so that repair failures or timeouts are logged and don't crash the retry logic.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Running subprocess.run in an async method will block the event loop—consider using asyncio.create_subprocess_exec or run the repair command in an executor to avoid blocking.
- Wrap the repair subprocess invocation in its own try/except (and avoid shell=True if possible) so that repair failures or timeouts are logged and don't crash the retry logic.
## Individual Comments
### Comment 1
<location> `src/mcp/client.py:85-86` </location>
<code_context>
- logger.error(f"Error getting tools from server {server_name}: {e}")
- return []
+ if self._is_mcp_error(e) and server_name in self._repair_commands:
+ subprocess.run(
+ self._repair_commands[server_name], shell=True, timeout=300
+ )
+ tools = await self.get_tools(server_name=server_name)
</code_context>
<issue_to_address>
**🚨 issue (security):** Using subprocess.run with shell=True introduces security risks.
If repair_command can be influenced by user input, this may allow shell injection. Use shell=False with a list of arguments, or strictly validate repair_command.
</issue_to_address>
### Comment 2
<location> `src/mcp/factory.py:79-80` </location>
<code_context>
server_config[name] = server_dict
+ # Store repair command if available
+ if server.repair_command:
+ repair_commands[name] = server.repair_command
+
# Tool filtering configuration
</code_context>
<issue_to_address>
**🚨 suggestion (security):** No validation of repair_command format or safety.
If repair_command comes from user input, validate its format or restrict allowed commands to mitigate misconfiguration and security risks.
Suggested implementation:
```python
import re
ALLOWED_REPAIR_COMMANDS = [
"restart-service",
"clear-cache",
"reindex-db",
# Add more allowed commands as needed
]
def is_valid_repair_command(cmd):
# Only allow exact matches from the whitelist, or simple shell-safe patterns
return cmd in ALLOWED_REPAIR_COMMANDS
for name, server in config.servers.items():
# Skip disabled servers
server_config[name] = server_dict
# Store repair command if available, with validation
if server.repair_command:
if is_valid_repair_command(server.repair_command):
repair_commands[name] = server.repair_command
else:
# Optionally log or raise an error here
# print(f"Invalid repair_command for server {name}: {server.repair_command}")
pass
# Tool filtering configuration
```
- You may want to replace the `ALLOWED_REPAIR_COMMANDS` list with a more dynamic or configurable source.
- If you want to allow more complex validation (e.g., regex patterns), update `is_valid_repair_command` accordingly.
- Consider logging or raising an exception if an invalid command is detected, depending on your application's error handling policy.
</issue_to_address>
### Comment 3
<location> `src/mcp/client.py:85-87` </location>
<code_context>
subprocess.run(
self._repair_commands[server_name], shell=True, timeout=300
)
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
*Source: opengrep*
</issue_to_address>
### Comment 4
<location> `src/mcp/client.py:86` </location>
<code_context>
self._repair_commands[server_name], shell=True, timeout=300
</code_context>
<issue_to_address>
**security (python.lang.security.audit.subprocess-shell-true):** Found 'subprocess' function 'run' with 'shell=True'. This is dangerous because this call will spawn the command using a shell process. Doing so propagates current shell settings and variables, which makes it much easier for a malicious actor to execute commands. Use 'shell=False' instead.
```suggestion
self._repair_commands[server_name], shell=False, timeout=300
```
*Source: opengrep*
</issue_to_address>
### Comment 5
<location> `src/mcp/client.py:75-77` </location>
<code_context>
def _filter_tools(self, tools: list[BaseTool], server_name: str) -> list[BaseTool]:
if server_name not in self._tool_filters:
return tools
filters = self._tool_filters[server_name]
include, exclude = filters.get("include", []), filters.get("exclude", [])
if include and exclude:
raise ValueError(
f"Cannot specify both include and exclude for server {server_name}"
)
if include:
return [t for t in tools if t.name in include]
if exclude:
return [t for t in tools if t.name not in exclude]
return tools
</code_context>
<issue_to_address>
**suggestion (code-quality):** We've found these issues:
- Lift code into else after jump in control flow ([`reintroduce-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/reintroduce-else/))
- Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))
```suggestion
return [t for t in tools if t.name not in exclude] if exclude else tools
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
783d711 to
0fbd765
Compare
0fbd765 to
d5d01da
Compare
feat: add repair command for MCP before retrying
Summary by Sourcery
Allow specifying a shell
repair_commandin the server configuration to automatically run recovery steps when fetching tools fails due to MCP errors, then retry retrieval; streamline filtering and error detection logic.New Features:
Enhancements:
_filter_toolsmethod_is_mcp_errorhelper to detectMcpErrorinstances and handle nestedExceptionGroup