-
Notifications
You must be signed in to change notification settings - Fork 107
chore: optimize work item property discovery and scope resolution #144
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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>"') | ||
| """ | ||
| 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: | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t swallow all project-listing errors as empty results. Returning 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: (BLE001) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| # Try type-scoped project endpoint first | ||
| project_props = client.work_item_properties.list( | ||
| workspace_slug=workspace_slug, | ||
|
|
@@ -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( | ||
|
|
||
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.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Add explicit
Args:andReturns:sections to this tool docstring.This updated tool docstring describes behavior well, but it still misses the required structured
Args/Returnssections.As per coding guidelines, “Tool docstrings must include Args and Returns sections.”
🤖 Prompt for AI Agents
Source: Coding guidelines