Conversation
Wire up the plane-sdk's existing Epic support as MCP tools, adding list_epics and retrieve_epic with full query parameter support (pagination, expand, fields, order_by). Closes makeplane#70 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a new epic tools module and registers five epic-related tools (list/create/retrieve/update/delete) with the MCP; updates the tool registry to call the new registration and extends integration tests to exercise epic lifecycle operations. Changes
Sequence Diagram(s)sequenceDiagram
participant Agent as Agent / Test
participant MCP as Plane MCP (tool)
participant Context as get_plane_client_context
participant Client as Plane Client
participant API as Plane API
Agent->>MCP: call create_epic(project_id, name, ...)
MCP->>Context: resolve workspace & project context
Context-->>MCP: workspace_slug, client
MCP->>Client: client.epics.create(...) color:rgba(66,133,244,0.5)
Client->>API: POST /workspaces/{slug}/epics/
API-->>Client: 201 Created (Epic)
Client-->>MCP: created epic payload
MCP-->>Agent: returns Epic
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 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 |
Expand from read-only (list/retrieve) to full CRUD: - create_epic: resolves epic work item type, creates via work items API - retrieve_epic: dedicated epic endpoint with expand/fields support - update_epic: updates via work items API, returns epic model - delete_epic: deletes underlying work item - list_epics: paginated with cursor, expand, fields, order_by Add integration test coverage for the full epic lifecycle (create, parent assignment, list, delete) and register all 5 tools in the EXPECTED_TOOLS availability check. Closes makeplane#70 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
plane_mcp/tools/epics.py (2)
33-72: Consider returning pagination metadata, not just results.
cursor/per_pageare accepted, but only a list is returned. Callers can’t discover a next cursor from the response, which limits true pagination. Consider returning pagination metadata alongside results if you intend cursor-based paging.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plane_mcp/tools/epics.py` around lines 33 - 72, The function list_epics currently returns only response.results which loses pagination metadata; change it to return the full PaginatedEpicResponse (or a tuple of results and metadata) so callers can access cursor/next cursor and per_page info. Update the return type annotation of list_epics accordingly and return the response object (or (response.results, response) if you prefer keeping a list-first API); adjust any callers of list_epics to unpack the new return shape. Ensure the code paths using the response variable (response: PaginatedEpicResponse) are updated to use its pagination fields instead of relying solely on the list.
242-245: Ruff TRY003: consider centralizing long exception messages.
Ruff flags long inline messages here (and similarly in the missing-epic-type check). If you enforce TRY003, consider using a dedicated exception class or message constant for reuse.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plane_mcp/tools/epics.py` around lines 242 - 245, The long inline exception message in the priority validation (using valid_priorities, PriorityEnum and validated_priority) should be centralized: create a single reusable message constant (e.g., INVALID_PRIORITY_MSG) or a small custom exception class (e.g., InvalidPriorityError) and replace the inline raise ValueError(...) with raise InvalidPriorityError(INVALID_PRIORITY_MSG) or raise ValueError(INVALID_PRIORITY_MSG); do the same for the similar missing-epic-type check so both places reference the shared constant/class instead of duplicating the long message.
🤖 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/epics.py`:
- Around line 124-140: create_epic currently silences invalid priorities by
setting validated_priority to None; change it to mirror update_epic by
validating the priority and raising a ValueError for invalid inputs instead of
dropping them. Specifically, in create_epic replace the validated_priority
assignment logic (the block using PriorityEnum and get_args and the
validated_priority variable) with an explicit validation that checks if priority
is in get_args(PriorityEnum) and if not raises ValueError("Invalid priority:
{priority}"), otherwise use the validated value when constructing CreateWorkItem
(type_id=epic_type.id, priority=validated_priority).
In `@tests/test_integration.py`:
- Around line 155-165: After calling list_epics and extracting epics, add an
assertion that the epic you created earlier is present in that list: check that
the created epic's id (e.g., created_epic_id or the id from the variable used
when creating the epic such as epic['id']) appears in [e['id'] for e in epics];
update the test around the epics_result / extract_result / epics block to
include this assertion (use list_epics, epics_result, extract_result, and epics
to locate the right spot).
---
Nitpick comments:
In `@plane_mcp/tools/epics.py`:
- Around line 33-72: The function list_epics currently returns only
response.results which loses pagination metadata; change it to return the full
PaginatedEpicResponse (or a tuple of results and metadata) so callers can access
cursor/next cursor and per_page info. Update the return type annotation of
list_epics accordingly and return the response object (or (response.results,
response) if you prefer keeping a list-first API); adjust any callers of
list_epics to unpack the new return shape. Ensure the code paths using the
response variable (response: PaginatedEpicResponse) are updated to use its
pagination fields instead of relying solely on the list.
- Around line 242-245: The long inline exception message in the priority
validation (using valid_priorities, PriorityEnum and validated_priority) should
be centralized: create a single reusable message constant (e.g.,
INVALID_PRIORITY_MSG) or a small custom exception class (e.g.,
InvalidPriorityError) and replace the inline raise ValueError(...) with raise
InvalidPriorityError(INVALID_PRIORITY_MSG) or raise
ValueError(INVALID_PRIORITY_MSG); do the same for the similar missing-epic-type
check so both places reference the shared constant/class instead of duplicating
the long message.
- Raise ValueError on invalid priority in create_epic (matching update_epic behavior) instead of silently dropping to None - Assert created epic appears in list_epics results Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
plane_mcp/tools/epics.py (1)
126-131: Ruff TRY003: long ValueError messages.Ruff flags the long inline messages here. If the project enforces TRY003, consider a short message or a small custom exception type to satisfy the rule.
Also applies to: 243-246
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plane_mcp/tools/epics.py` around lines 126 - 131, Replace the long inline ValueError messages with short messages or a small custom exception type: for the raise ValueError("No work item type with is_epic=True found in the project") replace the string with a concise message such as "No epic work item type found" or define and raise a custom NoEpicWorkItemTypeError; for the priority check that raises ValueError(f"Invalid priority '{priority}'. Must be one of: {valid_priorities}") either shorten to "Invalid priority" or create and raise an InvalidPriorityError and include full details in the exception instance only (not the inline string). Update usages around get_args, PriorityEnum, validated_priority and any similar raise ValueError occurrences (notably the repeated block at lines 243-246) to use the new short messages or custom exceptions.
🤖 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/epics.py`:
- Around line 34-72: The function list_epics currently returns only
response.results which drops pagination metadata; change list_epics to return
the full PaginatedEpicResponse (or a small wrapper containing results plus
cursor/total) instead of just list[Epic], update the return type annotation from
list[Epic] to PaginatedEpicResponse (or the wrapper type), and return the entire
response object (not response.results); also update any callers of list_epics to
consume the new shape (e.g., response.results and response.cursor/total) so
pagination metadata is preserved.
---
Nitpick comments:
In `@plane_mcp/tools/epics.py`:
- Around line 126-131: Replace the long inline ValueError messages with short
messages or a small custom exception type: for the raise ValueError("No work
item type with is_epic=True found in the project") replace the string with a
concise message such as "No epic work item type found" or define and raise a
custom NoEpicWorkItemTypeError; for the priority check that raises
ValueError(f"Invalid priority '{priority}'. Must be one of: {valid_priorities}")
either shorten to "Invalid priority" or create and raise an InvalidPriorityError
and include full details in the exception instance only (not the inline string).
Update usages around get_args, PriorityEnum, validated_priority and any similar
raise ValueError occurrences (notably the repeated block at lines 243-246) to
use the new short messages or custom exceptions.
Summary
list_epics,create_epic,retrieve_epic,update_epic,delete_epictype_id, and list/retrieve use the dedicated epic endpoints fromplane-sdkEXPECTED_TOOLSavailability checkDetails
The
plane-sdkv0.2.2 exposesclient.epics.list()andclient.epics.retrieve()for dedicated read access, but epics are created/updated/deleted as work items with an epic type. This PR bridges that gap with convenience tools that handle the type resolution automatically:list_epics(project_id, ...)client.epics.list()with cursor, expand, fields, order_bycreate_epic(project_id, name, ...)WorkItemType, creates viaclient.work_items.create(), returnsEpicretrieve_epic(project_id, epic_id, ...)client.epics.retrieve()with expand/fields supportupdate_epic(project_id, epic_id, ...)client.work_items.update(), returnsEpicdelete_epic(project_id, epic_id)client.work_items.delete()Closes #70
Test plan
EXPECTED_TOOLSavailability testcreate_epicresolves epic type and creates successfullylist_epicsreturns the created epicupdate_epicmodifies and returns updated epicretrieve_epicreturns a single epic by IDdelete_epicremoves the epicpytest tests/)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests