feat: add tools for reading work item property values#117
feat: add tools for reading work item property values#117151813125 wants to merge 2 commits intomakeplane:mainfrom
Conversation
Add `list_work_item_property_values` and `retrieve_work_item_property_value` MCP tools that wrap the existing `client.work_item_properties.values` SDK resource. The aggregating tool resolves the work item type internally and returns a flat list of properties currently set on the item, including member-type custom fields (e.g. "Responsible", "Review") that are not exposed via `retrieve_work_item` or any `expand` value. The tools return plain dicts so that the MCP output schema does not inherit Pydantic models with required fields that may be null in some backend states. Read-only — no create/update/delete tools are added.
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughTwo new MCP tools are added to retrieve work-item custom property values. One lists all properties for a work item with their values, handling multiple API calls and normalizing enum fields. The other retrieves a specific property's value, supporting both single and list return shapes, with a utility function to convert enum-like fields to primitives. Changes
Sequence DiagramsequenceDiagram
participant MCP as MCP Tool
participant API as Plane API
participant Response as Response Handler
MCP->>API: Get work item type ID
API-->>MCP: type_id
MCP->>API: List property definitions for type
API-->>MCP: property definitions list
loop For each property definition
MCP->>API: Fetch property value (may 404)
API-->>MCP: property value or 404
MCP->>Response: Suppress 404, normalize result
Response-->>MCP: normalized property data
end
MCP->>Response: Convert enums, aggregate results
Response-->>MCP: finalized property list
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@plane_mcp/tools/work_item_properties.py`:
- Around line 280-285: The function list_work_item_property_values currently
returns plain dict/list types; change its signature and implementation to return
the appropriate plane-sdk Pydantic model(s) (e.g., replace list[dict[str, Any]]
with list[WorkItemPropertyValue] or Optional[WorkItemPropertyValue] as
appropriate), import the WorkItemPropertyValue model from plane-sdk, and convert
returned dicts into model instances using WorkItemPropertyValue.parse_obj(...)
(or construct) before returning; make the same adjustment for other functions in
this module that currently return raw dict/list/None so all tool functions
comply with the "Tool functions must return Pydantic models from plane-sdk"
contract.
- Around line 347-350: The except HttpError blocks that catch 404 and set
value_obj = None incorrectly treat invalid work_item_id/property_id as "unset" —
locate the two except HttpError as exc blocks (currently checking
exc.status_code != 404 and setting value_obj = None) and remove the
special-casing for 404 so that HttpError is re-raised for invalid IDs; instead,
only handle non-HttpError/no-error cases where the API returns 200 with a
null/empty body as an unset value. Ensure both occurrences (the blocks at the
shown diff and the similar block later) are updated so 404 errors bubble up
rather than being converted to a "value not set" result.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 00ae7cb8-9c2d-45d9-b55e-f72740cc8c94
📒 Files selected for processing (1)
plane_mcp/tools/work_item_properties.py
| except HttpError as exc: | ||
| if exc.status_code != 404: | ||
| raise | ||
| value_obj = None |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
For the Plane Python SDK, what exact HttpError fields and payload/message are returned by work_item_properties.values.retrieve for (1) value not set and (2) invalid work_item_id/property_id 404s?
💡 Result:
For the Plane Python SDK, work_item_properties.values.retrieve raises an HttpError exception on non-2xx HTTP responses, including 404s for invalid work_item_id or property_id. HttpError fields (from SDK documentation and examples): - message: A string describing the error (e.g., the API error message like "The requested resource doesn't exist." for 404). - status_code: The HTTP status code (404 for both invalid work_item_id and invalid property_id cases). - response: The full HTTP response object, containing the raw API response body (JSON payload). (1) Value not set: No error is raised; returns a successful response (likely 200 OK with empty/null value or empty list for multi-value properties, as the endpoint retrieves values if they exist, but docs show it returns the value object when present. "Value not set" is handled as success with null/empty value, not a 404). (2) Invalid work_item_id or property_id: Raises HttpError with status_code=404. The API returns 404 "Not Found" when the requested resource (work item, property, or value) doesn't exist. Payload typically JSON like {"code": "not_found" or similar error code, "message": "The requested resource doesn't exist.", "details": {}} based on Plane API error format.
Citations:
- 1: https://github.com/makeplane/plane-python-sdk
- 2: https://developers.plane.so/api-reference/issue-types/values/get-property-value-detail
- 3: https://pypi.org/project/plane-sdk/
- 4: https://docs.plane.com/reference/errors
🏁 Script executed:
cd plane_mcp && find . -name "work_item_properties.py" -type fRepository: makeplane/plane-mcp-server
Length of output: 102
🏁 Script executed:
wc -l plane_mcp/tools/work_item_properties.pyRepository: makeplane/plane-mcp-server
Length of output: 114
🏁 Script executed:
sed -n '340,415p' plane_mcp/tools/work_item_properties.pyRepository: makeplane/plane-mcp-server
Length of output: 2798
🏁 Script executed:
rg -i "property value not set" --type pyRepository: makeplane/plane-mcp-server
Length of output: 52
🏁 Script executed:
rg "work_item_properties.*values" --type py -A 3 -B 3Repository: makeplane/plane-mcp-server
Length of output: 1207
🏁 Script executed:
# Search for any API error handling patterns in the codebase
rg "HttpError" plane_mcp/tools/ --type py -B 3 -A 3 | head -100Repository: makeplane/plane-mcp-server
Length of output: 1598
🏁 Script executed:
# Search for Plane SDK documentation or source about work_item_properties error handling
web_search
Plane Python SDK work_item_properties.values.retrieve API behavior 404 error when property value not set vs invalid IDRepository: makeplane/plane-mcp-server
Length of output: 165
🏁 Script executed:
# Also check if there are any tests in this repo that show the expected behavior
fd -type f -name "*.py" | xargs rg -l "work_item_properties" | head -20Repository: makeplane/plane-mcp-server
Length of output: 363
🏁 Script executed:
# Search for test files or examples
fd -type f -name "*test*.py" -o -name "*example*.py" | xargs rg "work_item_properties.values.retrieve" 2>/dev/null || echo "No test files found"Repository: makeplane/plane-mcp-server
Length of output: 481
🌐 Web query:
Plane Python SDK work_item_properties values retrieve 404 error behavior documentation
💡 Result:
Plane Python SDK: work_item_properties retrieve 404 behavior What I found in authoritative docs/code 1) The Python SDK exposes work_item_properties.retrieve(...), mapping to Plane’s “Retrieve a custom property” endpoint: GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-item-types/{type_id}/work-item-properties/{property_id}/ The docs show this endpoint and its 200 response shape, but they do not document what 404 returns or its exact body schema. 2) Error handling in the SDK: The SDK raises plane.errors.HttpError on non-2xx responses, including the HTTP status_code and response body. So for a 404 from the retrieve endpoint, you should expect an exception (HttpError) containing status_code=404 and the server’s response payload (if any). 3) Plane docs for custom property retrieval do not specify 404 semantics. They only document the request path parameters (including workspace_slug, project_id, type_id, property_id) and the 200 response. Practical interpretation of “why you get 404” Because the docs don’t define 404 behavior, the most evidence-backed conclusion is: - A 404 occurs when the custom property resource identified by (workspace_slug, project_id, type_id, property_id) does not exist or is not addressable under that combination. - The SDK will surface that as an HttpError exception rather than returning None. Example: handling the 404 in the Plane Python SDK try: prop = client.work_item_properties.retrieve( workspace_slug, project_id, work_item_type_id, property_id, ) except HttpError as e: if e.status_code == 404: # property not found / not accessible print("Not found") else: raise If you want, share the exact call signature (values you pass for workspace_slug, project_id, type_id, property_id) and I can pinpoint the most likely mismatch causing the 404.
Citations:
- 1: https://github.com/makeplane/plane-python-sdk
- 2: https://docs.plane.so/core-concepts/issues/properties
- 3: https://docs.plane.so/api-reference/issue-types/properties/get-property-details
- 4: https://github.com/makeplane/plane-python-sdk/blob/main/README.md
- 5: https://pypi.org/project/plane-sdk/0.2.9/
- 6: https://developers.plane.so/api-reference/issue-types/properties/get-property-details
🏁 Script executed:
# Check if there are any integration tests showing expected behavior
cat tests/test_integration.py | head -150Repository: makeplane/plane-mcp-server
Length of output: 4716
🏁 Script executed:
# Try to find any Plane SDK references or documentation about error messages
rg "Property value not set" . -i 2>/dev/null || echo "Pattern not found in repo"Repository: makeplane/plane-mcp-server
Length of output: 96
🏁 Script executed:
# Check if there's documentation about API error messages in the repo
find . -name "*.md" -o -name "*.rst" | xargs grep -i "404" 2>/dev/null | head -20Repository: makeplane/plane-mcp-server
Length of output: 52
🌐 Web query:
Plane backend API work_item_properties values endpoint 404 error response message format
💡 Result:
The Plane backend API (https://api.plane.so/api/v1/) returns a 404 Not Found status code with a JSON error response when the work_item_properties values endpoint is not found. This typically occurs if the requested workspace_slug, project_id, work_item_id, or property_id does not exist. The standard error response format for Plane API 4xx errors, including 404, is a JSON object containing: - code: A short string indicating the specific error code (e.g., a machine-readable identifier). - message or detail: A human-readable message providing more details about the error. - details: An optional object with key-value pairs providing additional information (may be empty {}). Example structure (inferred from Plane documentation patterns): { "code": "not_found", "message": "The requested resource doesn't exist.", "details": {} } No exact 404 example for this specific endpoint was found in the official docs, but this format is consistently described across Plane's error handling documentation. The endpoint likely follows the pattern /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/work-item-properties/{property_id}/values/, and 404 indicates one of the path parameters is invalid.
Citations:
- 1: https://docs.plane.com/reference/errors
- 2: https://docs.plane.so/api-reference/introduction
- 3: https://developers.plane.so/api-reference/issue-types/values/list-property-values
- 4: https://developers.plane.so/api-reference/issue-types/values/add-property-values
- 5: https://docs.plane.so/api-reference/issue-types/properties/get-property-details
Distinguish between invalid IDs and unset property values.
The code at lines 347-350 and 406-409 treats all 404 errors as "value not set," but the Plane API returns 404 when the work_item_id or property_id is invalid, not when a value is unset. Unset values are returned as 200 OK with a null/empty response. Re-raise 404 errors to surface invalid resource IDs to the caller instead of silently treating them as unset.
The suggested guard pattern checking for "Property value not set" in the error message won't work—the API returns a generic 404 error format without that specific message. Consider instead removing the 404 catch entirely and letting invalid IDs surface as errors.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@plane_mcp/tools/work_item_properties.py` around lines 347 - 350, The except
HttpError blocks that catch 404 and set value_obj = None incorrectly treat
invalid work_item_id/property_id as "unset" — locate the two except HttpError as
exc blocks (currently checking exc.status_code != 404 and setting value_obj =
None) and remove the special-casing for 404 so that HttpError is re-raised for
invalid IDs; instead, only handle non-HttpError/no-error cases where the API
returns 200 with a null/empty body as an unset value. Ensure both occurrences
(the blocks at the shown diff and the similar block later) are updated so 404
errors bubble up rather than being converted to a "value not set" result.
* `list_work_item_property_values` now returns `list[WorkItemPropertyValueDetail]` and `retrieve_work_item_property_value` returns `WorkItemPropertyValueDetail | list[WorkItemPropertyValueDetail]`, satisfying the "tool functions must return plane-sdk Pydantic models" contract. The aggregating tool no longer attempts to merge property metadata into a custom dict shape — callers that need `display_name` can pair the result with `list_work_item_properties(project_id, type_id)`. * `retrieve_work_item_property_value` no longer swallows 404. The Plane API returns 404 both for unset values and for invalid `work_item_id` / `property_id`, so silently mapping it to `None` would hide real errors; the underlying `HttpError` is now propagated unchanged. The granular tool's docstring documents this and points callers at `list_work_item_property_values` when they need a 404-free enumeration. * `list_work_item_property_values` keeps the 404→skip behaviour but only because the IDs it forwards to `values.retrieve` are guaranteed valid by construction (the work item was just retrieved successfully and the property comes from the authoritative type-bound listing). The reasoning is now spelled out in an inline comment so future readers do not have to re-derive the invariant. * Drop the no-longer-needed `_enum_value` helper and the `include_unset` parameter (which had no meaning once metadata fields are no longer in the returned shape).
|
Thanks for the review! Pushed 1. Pydantic return types. Both tools now return plane-sdk models:
Removed the dropping of property metadata into custom dict shape (along with the now-unused 2. 404 handling.
A note on the empirical behaviour I observed on Plane self-hosted v1.3.0 (in case it's useful): when the property has no value, the API returns |
Summary
Add two read-only MCP tools that expose work item custom property values (e.g.
Responsible,Review, or any other RELATION/OPTION/TEXT… property). Both tools wrap the existingclient.work_item_properties.valuesresource that already lives inplane-sdkbut was previously not exposed through MCP.list_work_item_property_values(project_id, work_item_id, include_unset=False)— high-level helper. Resolves the work item'stype_idinternally, lists all active property definitions for that type, and returns a flat list of{property_id, display_name, name, property_type, relation_type, is_multi, value, value_record_ids}. By default only properties with a set value are returned; passinclude_unset=Trueto also list active properties without values.retrieve_work_item_property_value(project_id, work_item_id, property_id)— granular retrieve. Returns a dict mirroringWorkItemPropertyValueDetailfor single-value properties, a list of dicts for multi-value properties, andNoneif the value is not set (Plane returns404 \"Property value not set\"in that case, and we trap it).No write tools (
set/delete) are added in this PR — kept intentionally minimal.Motivation
Member-type custom fields (RELATION/USER) are not returned by
retrieve_work_itemunder any value ofexpand(property_values,properties,issue_properties,custom_propertieswere all tried — none populate the field on the response). Until now, MCP clients had no way to read these properties at all, even though the underlying Plane API and the SDK already support it (/work-items/{id}/work-item-properties/{property_id}/values/).Implementation notes
The tools return plain
dict[str, Any]rather than the SDK Pydantic models. This is intentional and avoids inheriting the FastMCP output-validation issue described in #78:WorkItemPropertydeclaresdisplay_name: strandproperty_type: PropertyTypeas required, but the backend can legitimately returnnullin adjacent fields (description,external_source,external_id, …), which makes FastMCP's auto-generated JSON Schema reject otherwise-valid responses withOutput validation error: None is not of type 'string'. By returning hand-built dicts, the new tools sidestep that path entirely.Related: #78 (same root cause; this PR does not fix it but demonstrates a workaround that future PRs can apply more broadly).
Test plan
python -m py_compileandruff checkclean on the modified file.list_work_item_property_valuesreturns the correct UUIDs forResponsibleandReview(RELATION/USER properties on a work item that has them set).include_unset=Trueadds an entry for an unset OPTION property withvalue: null.retrieve_work_item_property_valuereturns the expectedWorkItemPropertyValueDetailpayload for a property with a value, andNonefor a property without one (404 from the API is trapped).*_work_item_propertyCRUD tools afterregister_work_item_property_tools(mcp).Summary by CodeRabbit