feat: add pythonic_check MCP tool for Python idiom analysis#49
Conversation
Add new MCP tool that detects non-idiomatic Python patterns and suggests Pythonic alternatives. Detects 10+ anti-patterns including: - range(len()) loops → enumerate() - dict.keys() iteration → direct dict iteration - == None comparisons → is None - Mutable default arguments (common bug source) - len(x) == 0 → truthiness checks - type() comparisons → isinstance() - Redundant boolean returns - Bare except clauses Includes 29 test cases covering all patterns and edge cases. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Review Summary by QodoAdd pythonic_check MCP tool for Python idiom analysis
WalkthroughsDescription• Adds new pythonic_check MCP tool for analyzing Python code idioms • Detects 10+ anti-patterns including range(len()), mutable defaults, == None • Provides actionable suggestions with Pythonic alternatives • Includes 29 comprehensive test cases covering all patterns and edge cases Diagramflowchart LR
A["Python Source Code"] -->|PythonicChecker| B["AST Analysis"]
B -->|Pattern Detection| C["Loop Patterns"]
B -->|Pattern Detection| D["Comparison Patterns"]
B -->|Pattern Detection| E["Mutable Defaults"]
B -->|Pattern Detection| F["Collection Building"]
B -->|Pattern Detection| G["Redundant Code"]
B -->|Pattern Detection| H["Exception Patterns"]
C --> I["Issues List"]
D --> I
E --> I
F --> I
G --> I
H --> I
I -->|MCP Server| J["JSON Response"]
File Changes1. src/workshop_mcp/pythonic_check/__init__.py
|
Code Review by Qodo
1. Type comparison check disabled
|
Claude Code Opus 4.5 - Code reviewFound 1 issue:
The python-mcp-agent-workshop/src/workshop_mcp/pythonic_check/pythonic_checker.py Lines 449 to 451 in 1af28f8 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Codex CLI GPT 5.2 Medium - Code Review SummaryThe FindingsP2 — Accept empty
|
PathValidator.validate() and validate_exists() return resolved Path objects after symlink resolution, but the returned values were being discarded. The code then used the original user-provided strings for file operations, creating a Time-of-Check-Time-of-Use vulnerability. Changes: - keyword_search: capture validated_paths from validate_multiple() and use validated_path_strings for tool execution - performance_check: capture validated_path from validate_exists() and use validated_file_path for PerformanceChecker and result output Addresses PR #49 review feedback. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add `-> None` return type annotation to comply with project requirement that all functions include type hints. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Codex CLI GPT-5.3 Code Review SummaryThanks for the addition of pythonic_check and the accompanying checker/test suite. I reviewed the PR diff against origin/main and found a few issues worth addressing before merge. Findings
——— Test Coverage Gaps
——— Suggested Fixes
|
Resolves Qodo review comment #2743700464 Priority: P0 (Security) PR: #49 Co-Authored-By: Claude Opus 4.6 <[email protected]> Entire-Checkpoint: 0bdb3ab1f37c
Resolves Qodo review comment #2743700467 Priority: P0 (Security) PR: #49 Co-Authored-By: Claude Opus 4.6 <[email protected]> Entire-Checkpoint: 0bdb3ab1f37c
Resolves Qodo review comment #2743700461 Priority: P1 (Reliability) PR: #49 Co-Authored-By: Claude Opus 4.6 <[email protected]> Entire-Checkpoint: 0bdb3ab1f37c
Move file I/O from PythonicChecker into the server handler, immediately after path validation. The validated canonical Path is now used for a single filesystem read, and only the content string is passed downstream. This eliminates the second filesystem access that created a window for symlink races between validation and use. Co-Authored-By: Claude Opus 4.6 <[email protected]> Entire-Checkpoint: d14b1011d0d5
- Replace raw PathValidationError messages with generic "Invalid file path" - Replace raw SecurityValidationError messages with generic "Security validation failed" - Log detailed errors internally for debugging while keeping client-facing messages safe - Prevents exposure of internal filesystem/security details to clients Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: c6f29cedfcc4
- Remove exception details from ValueError and SyntaxError logs (may contain user code) - Remove exception details from FileNotFoundError and KeyError logs - Log only exception type to remain useful for debugging - Prevents user-provided code fragments or sensitive details from appearing in logs Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: c6f29cedfcc4
- Add -> None return type annotations to all test methods - Ensures consistency with project-wide type-hint requirement - Improves mypy coverage even for test code Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: c6f29cedfcc4
- Reduce source_code description from 111 to under 100 characters - Maintains clarity while meeting Ruff formatting standards - Changed from "Optional Python source code string to analyze instead of file" to "Python source code string to analyze" Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: c6f29cedfcc4
- Change from truthiness checks to explicit is None checks - Allows source_code="" to be treated as valid empty source - PythonicChecker supports empty source and returns no issues - Fixes rejection of valid empty buffers/files Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: c6f29cedfcc4
- Catch UnicodeDecodeError when reading file as UTF-8 - Return proper -32602 client error instead of -32603 internal error - Non-UTF8 files are possible within allowed roots and should be handled gracefully - Provides clear error message: "File is not valid UTF-8" Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: c6f29cedfcc4
Qodo Fix SummaryReviewed and addressed Qodo review issues: ✅ Fixed IssuesSecurity (MEDIUM priority)
Correctness (MEDIUM/LOW priority)
ℹ️ Already Resolved (in previous commits)
Generated by Qodo PR Resolver skill via Claude Code |
|
/agentic_review |
|
Persistent review updated to latest commit 8780a45 |
| # Read file at the trust boundary using the validated path, | ||
| # then pass content downstream to avoid a second filesystem access (TOCTOU). | ||
| if validated_path: | ||
| logger.info("Executing pythonic check on file: %s", validated_path) | ||
| try: | ||
| source_code = Path(validated_path).read_text(encoding="utf-8") | ||
| except UnicodeDecodeError: | ||
| logger.warning("UnicodeDecodeError in pythonic_check") | ||
| return self._error_response( | ||
| request_id, | ||
| JsonRpcError(-32602, "File is not valid UTF-8"), | ||
| ) |
There was a problem hiding this comment.
1. File read errors uncaught 🐞 Bug ⛯ Reliability
_execute_pythonic_check reads the validated file path but only catches UnicodeDecodeError; FileNotFoundError/PermissionError/OSError can escape and potentially crash request handling. This can happen if the file disappears or becomes unreadable after validation.
Agent Prompt
### Issue description
`pythonic_check` reads file contents but only catches `UnicodeDecodeError`. Other I/O errors can escape the handler.
### Issue Context
Even after `validate_exists`, the file can be deleted/changed or become unreadable before `read_text` runs. This should return a structured JSON-RPC error instead of throwing.
### Fix Focus Areas
- src/workshop_mcp/server.py[655-669]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
…Error - Catch OSError (base class for FileNotFoundError, PermissionError, IsADirectoryError, etc.) - File can be deleted, made unreadable, or changed between validation and read - Returns structured -32602 error instead of throwing -32603 internal error - Logs exception type for debugging without exposing details Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: 0d3929a5b62c
Additional Fix AppliedI/O error handling gap closed - Added Even after
Now returns structured Commit: Generated by Claude Code |
- Capture and use validated path instead of discarding it (closes TOCTOU gap) - Fix wrong tool name in logs (was "pythonic_check", now "performance_check") - Add OSError and UnicodeDecodeError handling when reading files - Read file at trust boundary and pass source_code to avoid second filesystem access Fixes Qodo issues #8, #9, #11 (part of #1) Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: e8eac6ac9624
- Replace raw PathValidationError message with generic "Invalid file path" - Log detailed error internally for debugging while keeping client-facing message safe - Prevents exposure of internal filesystem/security details to clients Fixes Qodo issue #8/#9 for keyword_search handler Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: e8eac6ac9624
- Change from unsafe "x = x or []" pattern to safe "if x is None: x = []"
- Prevents semantic changes when callers pass empty collections
- Applies to list, dict, and set mutable default suggestions
The "x = x or []" pattern incorrectly creates a new collection when caller
passes an empty one ([], {}, or set()), changing semantics. The safe pattern
only creates a new collection when None is passed.
Fixes Qodo issue #13
Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: e8eac6ac9624
- Disable _check_dict_setitem_in_loop to avoid false positives - Check was flagging ALL subscript assignments (lists, arrays, etc.), not just dicts - Proper detection requires type inference which is complex - Better to avoid false positives than suggest incorrect dict comprehensions Fixes Qodo issue #21 Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: e8eac6ac9624
- Disable _check_type_comparison to avoid incorrect isinstance() suggestions - Check was flagging all type(x) == y comparisons regardless of whether y is a class - isinstance() is only valid when RHS is actually a class/type - Proper detection requires type inference which is complex Fixes Qodo issue #22 Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]> Entire-Checkpoint: e8eac6ac9624
Qodo Fix Summary - Round 2Fixed all 8 outstanding issues from the persistent Qodo review: ✅ Fixed IssuesCRITICAL (Security/Reliability)
MEDIUM (Correctness)
LOW (False Positives)
📊 CommitsAll fixes follow the same security and correctness patterns established in the initial round of fixes. Generated by Qodo PR Resolver skill via Claude Code |
|
/agentic_review |
|
Persistent review updated to latest commit bd8a190 |
PathValidator.validate() and validate_exists() return resolved Path objects after symlink resolution, but the returned values were being discarded. The code then used the original user-provided strings for file operations, creating a Time-of-Check-Time-of-Use vulnerability. Changes: - keyword_search: capture validated_paths from validate_multiple() and use validated_path_strings for tool execution - performance_check: capture validated_path from validate_exists() and use validated_file_path for PerformanceChecker and result output Addresses PR #49 review feedback. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Summary
pythonic_checkMCP tool that analyzes Python code for non-idiomatic patternsrange(len()), mutable defaults,== None, and morefile_pathorsource_codeinput, structured JSON output)Patterns Detected
for i in range(len(x))enumerate(x)for k in d.keys()for k in ddirectlyx == Nonex is Nonex == True/Falseif x:type(x) == Clsisinstance(x, Cls)len(x) == 0not xdef foo(items=[])items=Nonepatternresult.append()in loopif cond: return True else: return Falseexcept:Test plan
🤖 Generated with Claude Code