Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 33 additions & 14 deletions plane_mcp/tools/work_item_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,42 @@ def register_work_item_property_tools(mcp: FastMCP) -> None:

@mcp.tool()
def list_work_item_properties(
work_item_type_id: str,
work_item_type_id: str | None = None,
project_id: str | None = None,
params: dict[str, Any] | None = None,
) -> list[WorkItemProperty]:
"""
List custom properties for a work item type.
List custom work item properties.

Lookup order when project_id provided:
1. Type-scoped endpoint (properties explicitly linked to the type)
2. Flat project endpoint (properties created without type association)
3. Workspace-level endpoint (workspace-wide properties)
Scope resolution (highest priority first):
- no args → ALL workspace-level properties (one API call)
- work_item_type_id only → workspace-level properties linked to that type
- project_id only → all properties in that project (any type)
- project_id + work_item_type_id → properties linked to that type in that project,
falling back to project-flat then workspace if empty

Omit project_id to query workspace scope directly.
For PQL filtering by name, prefer calling with NO args — one workspace-wide
fetch beats iterating every work item type. Each result includes the
`display_name` you can match in-memory before composing `cf["<id>"]` in PQL.

Each result includes:
- id: property UUID — use as cf["<id>"] in PQL filters
- display_name: user-facing label (e.g. "Fed", "Acceptance Criteria")
- property_type: TEXT | OPTION | DECIMAL | BOOLEAN | DATETIME | RELATION | URL | EMAIL
- options: for OPTION type, each option has id + name; use option id in PQL

PQL workflow for filtering by custom property:
1. list_work_item_types(project_id) → get type UUIDs
2. list_work_item_properties(work_item_type_id, project_id) → get property + option UUIDs
3. list_work_items(pql='cf["<prop-uuid>"] = "<opt-uuid>"')
PQL workflow for filtering by custom property (efficient path):
1. list_work_item_properties() → all workspace properties, one call
2. find the property by display_name in-memory → property.id
3. list_work_items(pql='cf["<property.id>"] = "<option.id>"')
"""
Comment on lines 35 to 59

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add explicit Args: and Returns: sections to this tool docstring.

This updated tool docstring describes behavior well, but it still misses the required structured Args/Returns sections.

As per coding guidelines, “Tool docstrings must include Args and Returns sections.”

🤖 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 `@plane_mcp/tools/work_item_properties.py` around lines 35 - 59, Update the
docstring for the function list_work_item_properties to include explicit "Args:"
and "Returns:" sections: under Args list the function parameters (e.g.,
work_item_type_id, project_id — mark them optional and describe their effect on
scope resolution as already explained in the summary), and under Returns
describe the return value (e.g., list of property dicts with keys id,
display_name, property_type, and options and the shape of options for OPTION
types). Keep the existing explanatory text about scope and PQL workflow, but add
these two structured sections so the tool docstring conforms to the project's
docstring guidelines.

Source: Coding guidelines

client, workspace_slug = get_plane_client_context()

# Fast path — no args: return every workspace-level property in ONE call.
# Use this when resolving property UUIDs by display_name for PQL composition.
if not work_item_type_id and not project_id:
return client.workspace_work_item_properties.list(workspace_slug=workspace_slug)

def _get_workspace_props_for_type(type_id: str) -> list:
"""Fetch workspace-level properties associated with a type. Returns [] on any error."""
try:
Expand All @@ -73,6 +83,17 @@ def _get_workspace_props_for_type(type_id: str) -> list:
if not project_id:
return _get_workspace_props_for_type(work_item_type_id)

if not work_item_type_id:
# project-flat endpoint: all properties in the project, any type
try:
return client.work_item_properties.list_project(
workspace_slug=workspace_slug,
project_id=project_id,
params=params,
)
except Exception:
return []
Comment on lines +88 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t swallow all project-listing errors as empty results.

Returning [] on any exception here masks real failures (auth, network, server errors) as “no properties,” which can break downstream PQL discovery silently. Let unexpected errors propagate, or handle only explicitly expected cases.

Suggested fix
         if not work_item_type_id:
             # project-flat endpoint: all properties in the project, any type
-            try:
-                return client.work_item_properties.list_project(
-                    workspace_slug=workspace_slug,
-                    project_id=project_id,
-                    params=params,
-                )
-            except Exception:
-                return []
+            return client.work_item_properties.list_project(
+                workspace_slug=workspace_slug,
+                project_id=project_id,
+                params=params,
+            )
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 94-94: Do not catch blind exception: Exception

(BLE001)

🤖 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 `@plane_mcp/tools/work_item_properties.py` around lines 88 - 95, The current
try/except around client.work_item_properties.list_project swallows all
exceptions and returns an empty list; update the handler so real errors
propagate instead of being masked: remove the broad "except Exception: return
[]" or replace it with catching only the specific expected exception (e.g., a
NotFound or 404-equivalent from the client) and re-raise or log-and-rethrow for
everything else; ensure the call to client.work_item_properties.list_project
(using workspace_slug, project_id, params) either returns the real result or
raises the original error so downstream PQL discovery can react correctly.

Source: Linters/SAST tools


# Try type-scoped project endpoint first
project_props = client.work_item_properties.list(
workspace_slug=workspace_slug,
Expand Down Expand Up @@ -194,9 +215,7 @@ def create_work_item_property(
project_id=project_id,
data=data,
)
return client.workspace_work_item_properties.create(
workspace_slug=workspace_slug, data=data
)
return client.workspace_work_item_properties.create(workspace_slug=workspace_slug, data=data)

@mcp.tool()
def retrieve_work_item_property(
Expand Down
Loading