fix(mcp): skip non-dict models/columns/rels in list and describe#2547
fix(mcp): skip non-dict models/columns/rels in list and describe#2547Bartok9 wants to merge 2 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughThe MCP model tools now filter malformed models, columns, and relationships, normalize invalid column collections, preserve description fallbacks, and include unit tests for junk entries and safe column handling. ChangesMCP model data guards
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@core/wren/src/wren/mcp_server.py`:
- Around line 267-278: Prevent malformed scalar YAML values from reaching
collection operations: in core/wren/src/wren/mcp_server.py lines 267-278,
require model.get("columns") to be a list and require col.get("properties") to
be a dict before reading description; in lines 233-238, require
model.get("properties") to be a dict before accessing description; in lines
280-285, require load_relationships(ctx.project) and rel.get("models") to be
lists before iteration or membership checks. Mirror the properties guard in
core/wren/tests/unit/test_mcp_model_guards.py lines 9-14 and the columns guard
in lines 26-30.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: e5581b2e-6af5-4145-a445-18a7b903bea1
📒 Files selected for processing (2)
core/wren/src/wren/mcp_server.pycore/wren/tests/unit/test_mcp_model_guards.py
| columns = [] | ||
| for col in model.get("columns") or []: | ||
| if not isinstance(col, dict): | ||
| continue | ||
| columns.append( | ||
| { | ||
| "name": col.get("name"), | ||
| "type": col.get("type"), | ||
| "description": col.get("description") | ||
| or (col.get("properties") or {}).get("description"), | ||
| } | ||
| ) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Incomplete guards against scalar YAML values.
Based on the PR objectives to prevent failures from malformed context output, the current type checks are insufficient because they do not protect against truthy scalar types. If malformed YAML values for columns, properties, or relationships parse as integers or booleans, or [] and or {} will fall back to those exact scalars, leading to TypeError during iteration/membership tests or AttributeError when chaining .get(). Furthermore, unvalidated strings can cause unintended substring matches during in checks.
core/wren/src/wren/mcp_server.py#L267-L278: Guardmodel.get("columns")as a list before iterating, and verifycol.get("properties")is a dict before calling.get("description").core/wren/src/wren/mcp_server.py#L233-L238: Verifymodel.get("properties")is a dict before calling.get("description").core/wren/src/wren/mcp_server.py#L280-L285: Guardload_relationships(ctx.project)andrel.get("models")as lists before iterating or performinginchecks.core/wren/tests/unit/test_mcp_model_guards.py#L9-L14: Mirror thepropertiesdict check in the test helper.core/wren/tests/unit/test_mcp_model_guards.py#L26-L30: Mirror thecolumnslist check in the test helper.
🛡️ Proposed fixes
core/wren/src/wren/mcp_server.py
# Lines 233-238 (list_models fix)
- description = model.get("description")
- if description is None:
- description = (model.get("properties") or {}).get("description")
- columns = model.get("columns") or []
- if not isinstance(columns, list):
- columns = []
+ description = model.get("description")
+ if description is None:
+ props = model.get("properties")
+ if isinstance(props, dict):
+ description = props.get("description")
+ columns = model.get("columns")
+ if not isinstance(columns, list):
+ columns = []
# Lines 267-278 (describe_model columns fix)
- columns = []
- for col in model.get("columns") or []:
- if not isinstance(col, dict):
- continue
- columns.append(
- {
- "name": col.get("name"),
- "type": col.get("type"),
- "description": col.get("description")
- or (col.get("properties") or {}).get("description"),
- }
- )
+ columns = []
+ raw_columns = model.get("columns")
+ if isinstance(raw_columns, list):
+ for col in raw_columns:
+ if not isinstance(col, dict):
+ continue
+ desc = col.get("description")
+ if not desc:
+ props = col.get("properties")
+ if isinstance(props, dict):
+ desc = props.get("description")
+ columns.append(
+ {
+ "name": col.get("name"),
+ "type": col.get("type"),
+ "description": desc,
+ }
+ )
# Lines 280-285 (describe_model relationships fix)
- relationships = []
- for rel in load_relationships(ctx.project):
- if not isinstance(rel, dict):
- continue
- if name in (rel.get("models") or []):
- relationships.append(rel)
+ relationships = []
+ raw_rels = load_relationships(ctx.project)
+ if isinstance(raw_rels, list):
+ for rel in raw_rels:
+ if not isinstance(rel, dict):
+ continue
+ models_list = rel.get("models")
+ if isinstance(models_list, list) and name in models_list:
+ relationships.append(rel)core/wren/tests/unit/test_mcp_model_guards.py
# Lines 9-14 (test helper fix)
- description = model.get("description")
- if description is None:
- description = (model.get("properties") or {}).get("description")
- columns = model.get("columns") or []
- if not isinstance(columns, list):
- columns = []
+ description = model.get("description")
+ if description is None:
+ props = model.get("properties")
+ if isinstance(props, dict):
+ description = props.get("description")
+ columns = model.get("columns")
+ if not isinstance(columns, list):
+ columns = []
# Lines 26-30 (test helper fix)
- columns = []
- for col in model.get("columns") or []:
- if not isinstance(col, dict):
- continue
- columns.append({"name": col.get("name")})
+ columns = []
+ raw_columns = model.get("columns")
+ if isinstance(raw_columns, list):
+ for col in raw_columns:
+ if not isinstance(col, dict):
+ continue
+ columns.append({"name": col.get("name")})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| columns = [] | |
| for col in model.get("columns") or []: | |
| if not isinstance(col, dict): | |
| continue | |
| columns.append( | |
| { | |
| "name": col.get("name"), | |
| "type": col.get("type"), | |
| "description": col.get("description") | |
| or (col.get("properties") or {}).get("description"), | |
| } | |
| ) | |
| columns = [] | |
| raw_columns = model.get("columns") | |
| if isinstance(raw_columns, list): | |
| for col in raw_columns: | |
| if not isinstance(col, dict): | |
| continue | |
| desc = col.get("description") | |
| if not desc: | |
| props = col.get("properties") | |
| if isinstance(props, dict): | |
| desc = props.get("description") | |
| columns.append( | |
| { | |
| "name": col.get("name"), | |
| "type": col.get("type"), | |
| "description": desc, | |
| } | |
| ) |
📍 Affects 2 files
core/wren/src/wren/mcp_server.py#L267-L278(this comment)core/wren/src/wren/mcp_server.py#L233-L238core/wren/src/wren/mcp_server.py#L280-L285core/wren/tests/unit/test_mcp_model_guards.py#L9-L14core/wren/tests/unit/test_mcp_model_guards.py#L26-L30
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@core/wren/src/wren/mcp_server.py` around lines 267 - 278, Prevent malformed
scalar YAML values from reaching collection operations: in
core/wren/src/wren/mcp_server.py lines 267-278, require model.get("columns") to
be a list and require col.get("properties") to be a dict before reading
description; in lines 233-238, require model.get("properties") to be a dict
before accessing description; in lines 280-285, require
load_relationships(ctx.project) and rel.get("models") to be lists before
iteration or membership checks. Mirror the properties guard in
core/wren/tests/unit/test_mcp_model_guards.py lines 9-14 and the columns guard
in lines 26-30.
list_models/describe_model assumed every MDL row was a dict. Malformed context blobs then AttributeError mid-tool call. Skip junk rows.
1beb238 to
14ad14f
Compare
Summary
list_models/describe_modelcalled.geton every model/column/relationship without type checks.AttributeError.columnsas empty.Motivation
Defensive consistency with memory/schema_indexer guards on Apache-2.0
core/**. MCP tools should return partial valid inventories rather than fail closed on one corrupt row.Verification
(Logic mirror of the mcp_server sequence which is nested inside factory registration.)
Apache-2.0
Touches only
core/wren/**.Summary by CodeRabbit