Skip to content

feat: add epic CRUD tools#71

Open
stimothy wants to merge 3 commits intomakeplane:canaryfrom
stimothy:feat/epic-read-list-tools
Open

feat: add epic CRUD tools#71
stimothy wants to merge 3 commits intomakeplane:canaryfrom
stimothy:feat/epic-read-list-tools

Conversation

@stimothy
Copy link

@stimothy stimothy commented Feb 23, 2026

Summary

  • Add full CRUD epic tools: list_epics, create_epic, retrieve_epic, update_epic, delete_epic
  • Epics are work items with the epic type — create/update/delete operate through the work items API while automatically resolving the epic type_id, and list/retrieve use the dedicated epic endpoints from plane-sdk
  • Add integration test coverage for the complete epic lifecycle (create, parent assignment, list, delete)
  • Register all 5 tools in the EXPECTED_TOOLS availability check

Details

The plane-sdk v0.2.2 exposes client.epics.list() and client.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:

Tool What it does
list_epics(project_id, ...) Paginated list via client.epics.list() with cursor, expand, fields, order_by
create_epic(project_id, name, ...) Resolves epic WorkItemType, creates via client.work_items.create(), returns Epic
retrieve_epic(project_id, epic_id, ...) Fetches via client.epics.retrieve() with expand/fields support
update_epic(project_id, epic_id, ...) Updates via client.work_items.update(), returns Epic
delete_epic(project_id, epic_id) Deletes via client.work_items.delete()

Closes #70

Test plan

  • Verify all 5 epic tools register: EXPECTED_TOOLS availability test
  • create_epic resolves epic type and creates successfully
  • list_epics returns the created epic
  • update_epic modifies and returns updated epic
  • retrieve_epic returns a single epic by ID
  • delete_epic removes the epic
  • Existing tools remain unaffected (pytest tests/)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Full epic management: list (pagination/filtering/fields/ordering), create, retrieve, update, and delete epics.
    • Assign epics as parents of work items and manage epic metadata (assignees, labels, priority, dates, estimates, etc.).
  • Tests

    • Integration test updated to exercise epic lifecycle and verify epic-related tools are available.

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>
@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Tool Registration
plane_mcp/tools/__init__.py
Imports register_epic_tools and invokes register_epic_tools(mcp) inside register_tools, integrating epic tools into MCP registration.
Epic Tools Implementation
plane_mcp/tools/epics.py
New module adding register_epic_tools(mcp) which registers: list_epics, create_epic, retrieve_epic, update_epic, delete_epic. Implements epic-type resolution, priority validation, and uses get_plane_client_context and Plane models to call workspace-level epic APIs.
Tests / Expectations
tests/test_integration.py
Integration test expanded to exercise epic lifecycle (create, set parent on a work item, list, delete) and EXPECTED_TOOLS updated to include epic-related tool names.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through the code with a nimble little leap,

Epics to create, list, and keep,
Parents set tidy, then deleted with grace,
I left tiny pawprints all over this place — 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add epic CRUD tools' clearly and concisely summarizes the main change: adding Create, Read, Update, Delete operations for epics.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #70: list_epics and retrieve_epic using workspace-level epic endpoints, plus extends with create/update/delete operations. All tools properly call Plane SDK epic endpoints and handle workspace context.
Out of Scope Changes check ✅ Passed All changes directly support epic CRUD functionality: new epics.py module with five tools, integration of register_epic_tools into init.py, and test coverage for epic lifecycle. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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>
@stimothy stimothy changed the title feat: add epic list and retrieve MCP tools feat: add epic CRUD tools Feb 23, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
plane_mcp/tools/epics.py (2)

33-72: Consider returning pagination metadata, not just results.
cursor/per_page are 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.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4dca447 and 5e67d45.

📒 Files selected for processing (2)
  • plane_mcp/tools/epics.py
  • tests/test_integration.py

- 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>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e67d45 and 52b04f9.

📒 Files selected for processing (2)
  • plane_mcp/tools/epics.py
  • tests/test_integration.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add epic read/list support (GET /epics/ endpoints)

1 participant