Skip to content

feat: add milestone tools for managing epics and milestones#64

Merged
Prashant-Surya merged 5 commits intomakeplane:canaryfrom
osedudev:feature/milestone-api
Mar 3, 2026
Merged

feat: add milestone tools for managing epics and milestones#64
Prashant-Surya merged 5 commits intomakeplane:canaryfrom
osedudev:feature/milestone-api

Conversation

@jessedijkstra
Copy link
Contributor

@jessedijkstra jessedijkstra commented Feb 10, 2026

Summary

  • Adds MCP tools for milestone CRUD operations (list, create, retrieve, update, delete)
  • Adds tools for managing work items within milestones (add, remove, list)
  • Updates plane-sdk dependency to use the milestone-methods branch

Dependencies

  • plane-python-sdk: Depends on open PR makeplane/plane-python-sdk#17 which adds milestone API support to the SDK. The plane-sdk dependency in pyproject.toml points to that branch.
  • Epics PR: Based on the open epic tools PR #63

Test plan

  • Verify milestone CRUD operations work end-to-end
  • Verify adding/removing work items to/from milestones
  • Verify listing work items within a milestone
  • Merge plane-python-sdk#17 first, then update dependency to released version

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added epic management tools: list, create, update, retrieve, and delete epics.
    • Added milestone management tools: list, create, update, retrieve, and delete milestones.
    • Added ability to associate and manage work items within milestones.
  • Chores

    • Updated plane-sdk dependency to version 0.2.6.
  • Tests

    • Enhanced integration test coverage for epic and milestone operations.

@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

This pull request introduces epic and milestone management tools to the Plane MCP Server, registers them via the tool initialization module, updates the plane-sdk dependency to 0.2.6, adds configuration for uv 0.9.18, and expands integration tests to validate the new functionality.

Changes

Cohort / File(s) Summary
Configuration Updates
mise.toml, pyproject.toml
Added uv tool configuration (0.9.18) and bumped plane-sdk dependency from 0.2.2 to 0.2.6.
Tool Registration
plane_mcp/tools/__init__.py
Imported and registered two new tool setup functions: register_epic_tools and register_milestone_tools to extend the MCP server's tool suite.
Epic Management Tools
plane_mcp/tools/epics.py
New module providing CRUD operations for epics: list, create, retrieve, update, and delete, with validation against priority enums and epic type lookup during operations.
Milestone Management Tools
plane_mcp/tools/milestones.py
New module providing comprehensive milestone operations: list, create, retrieve, update, delete, and work item association/disassociation, using paginated responses and domain-specific data models.
Integration Tests
tests/test_integration.py
Expanded test workflow to cover epic creation, listing, reparenting work items to epics, milestone creation/association, work item management within milestones, and updated cleanup sequence.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~40 minutes

Poem

🐰 Epics and milestones hop into place,
Two sturdy tools to quicken the pace,
CRUD ops dance smoothly with Plane's grace,
Dependencies bumped, tests embrace,
The server now leaps with newfound space! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title mentions 'milestone tools' and 'epics and milestones', but the primary change is adding both epic and milestone management tools, not just milestones as the singular 'milestone tools' suggests.
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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

@Prashant-Surya
Copy link
Member

@jessedijkstra We published v0.2.6 of python sdk with milestone endpoints. Please update the code and provide testing details for us to start review. Thanks for your contribution 🙌

@jessedijkstra
Copy link
Contributor Author

@Prashant-Surya thanks! Will do

@jessedijkstra jessedijkstra marked this pull request as ready for review February 12, 2026 09:34
@jessedijkstra
Copy link
Contributor Author

@Prashant-Surya ready for review!

@sriramveeraghanta
Copy link
Member

@jessedijkstra can you check the merge conflict.

jessedijkstra and others added 5 commits March 3, 2026 14:58
Add MCP tools for epic management:
- list_epics: List all epics in a project
- create_epic: Create a new epic
- update_epic: Update an existing epic
- retrieve_epic: Retrieve a specific epic by ID

Also includes:
- Integration tests for epic operations
- mise.toml for uv version pinning

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Add deleting an epic

Update integration tests

Address PR review feedback for epic tools

- Fix docstrings to only list actual function parameters
- Add validation for epic work item type (raise ValueError if not found)
- Add priority validation with actionable error messages
- Fix update_epic return type from WorkItem to Epic
- Fix grammar: "a epic" -> "an epic"
- Refactor _get_epic_work_item_type_id to accept client/workspace params
- Remove unused WorkItem import
- Fix test to handle list_epics returning flat list (not paginated)
- Remove extraneous f-string prefixes in test prints
- Add delete_epic to EXPECTED_TOOLS

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Fix epics
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jessedijkstra jessedijkstra force-pushed the feature/milestone-api branch from 6c10eac to 1f62847 Compare March 3, 2026 13:59
@jessedijkstra jessedijkstra changed the title feat: add milestone tools for managing milestones feat: add milestone tools for managing epics and milestones Mar 3, 2026
@jessedijkstra
Copy link
Contributor Author

@sriramveeraghanta updated, could you merge?

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: 5

🤖 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 89-112: The docstring for create_epic is out of sync with the API:
remove non-existent parameters workspace_slug and type_id from the Args list,
ensure the Args section exactly lists the actual function parameters (e.g.,
project_id, name, assignees, labels, point, description_html,
description_stripped, priority, start_date, target_date, sort_order, is_draft,
external_source, external_id, parent, state, estimate_point), and update the
Returns section to indicate it returns an Epic (not WorkItem); keep both Args
and Returns sections present and concise to match coding guidelines and the
create_epic function signature.
- Around line 115-118: The code silently coerces invalid priority values to None
when constructing validated_priority; instead, in create_epic validate the
incoming priority against PriorityEnum (use get_args(PriorityEnum) or
PriorityEnum.__members__) and raise a clear exception (e.g., ValueError) if the
value is not allowed so the call fails fast; update the logic around
validated_priority in the create_epic implementation (and any helper validating
function) to perform this explicit check and raise rather than assigning None.

In `@plane_mcp/tools/milestones.py`:
- Around line 133-140: Update the docstrings for delete_milestone,
add_work_items_to_milestone, and remove_work_items_from_milestone to include a
Returns section; specifically, for delete_milestone and the two modify functions
add a "Returns:" entry that states the return type (None) and a brief
description such as "None — performs operation or raises on failure" or "None on
success." Edit the docstrings inside the corresponding function definitions
(delete_milestone, add_work_items_to_milestone,
remove_work_items_from_milestone) so they follow the existing Args/Returns
formatting used elsewhere in plane_mcp/tools/*.py.

In `@tests/test_integration.py`:
- Around line 176-183: The test is calling the "create_milestone" tool with
invalid argument names (sending name, description, and associated_work_item_ids)
which will fail; update the client.call_tool calls that invoke
"create_milestone" (e.g., where milestone_result is assigned) to use the tool's
expected arguments: replace "name" with "title", remove the unsupported
"description" and "associated_work_item_ids" keys, and supply only the
parameters the milestone tool accepts (keep "project_id" and any other supported
fields per the tool contract).
- Around line 362-368: The test's EXPECTED_TOOLS list omits the new milestone
tools so availability checks miss regressions; update the EXPECTED_TOOLS
constant used in tests/test_integration.py (symbol EXPECTED_TOOLS) to include
the milestone tool names — e.g., "list_milestones", "retrieve_milestone",
"create_milestone", "update_milestone", and "delete_milestone" — ensuring the
test validates registration of milestone-related tools alongside the epic tools.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe77c22 and 1f62847.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • mise.toml
  • plane_mcp/tools/__init__.py
  • plane_mcp/tools/epics.py
  • plane_mcp/tools/milestones.py
  • pyproject.toml
  • tests/test_integration.py

Comment on lines +89 to +112
Args:
workspace_slug: The workspace slug identifier
project_id: UUID of the project
name: Epic name (required)
assignees: List of user IDs to assign to the epic
labels: List of label IDs to attach to the epic
type_id: UUID of the epic type
point: Story point value
description_html: HTML description of the epic
description_stripped: Plain text description (stripped of HTML)
priority: Priority level (urgent, high, medium, low, none)
start_date: Start date (ISO 8601 format)
target_date: Target/end date (ISO 8601 format)
sort_order: Sort order value
is_draft: Whether the epic is a draft
external_source: External system source name
external_id: External system identifier
parent: UUID of the parent epic
state: UUID of the state
estimate_point: Estimate point value

Returns:
Created WorkItem object
"""
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix create_epic docstring to match the actual API.

The Args section includes non-existent parameters (workspace_slug, type_id), and Returns says “WorkItem” while the function returns Epic. This will mislead tool callers.

Suggested fix
         Args:
-            workspace_slug: The workspace slug identifier
             project_id: UUID of the project
             name: Epic name (required)
             assignees: List of user IDs to assign to the epic
             labels: List of label IDs to attach to the epic
-            type_id: UUID of the epic type
             point: Story point value
             description_html: HTML description of the epic
             description_stripped: Plain text description (stripped of HTML)
             priority: Priority level (urgent, high, medium, low, none)
@@
         Returns:
-            Created WorkItem object
+            Created Epic object
As per coding guidelines: “`plane_mcp/tools/*.py`: Tool docstrings must include Args and Returns sections.”
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Args:
workspace_slug: The workspace slug identifier
project_id: UUID of the project
name: Epic name (required)
assignees: List of user IDs to assign to the epic
labels: List of label IDs to attach to the epic
type_id: UUID of the epic type
point: Story point value
description_html: HTML description of the epic
description_stripped: Plain text description (stripped of HTML)
priority: Priority level (urgent, high, medium, low, none)
start_date: Start date (ISO 8601 format)
target_date: Target/end date (ISO 8601 format)
sort_order: Sort order value
is_draft: Whether the epic is a draft
external_source: External system source name
external_id: External system identifier
parent: UUID of the parent epic
state: UUID of the state
estimate_point: Estimate point value
Returns:
Created WorkItem object
"""
Args:
project_id: UUID of the project
name: Epic name (required)
assignees: List of user IDs to assign to the epic
labels: List of label IDs to attach to the epic
point: Story point value
description_html: HTML description of the epic
description_stripped: Plain text description (stripped of HTML)
priority: Priority level (urgent, high, medium, low, none)
start_date: Start date (ISO 8601 format)
target_date: Target/end date (ISO 8601 format)
sort_order: Sort order value
is_draft: Whether the epic is a draft
external_source: External system source name
external_id: External system identifier
parent: UUID of the parent epic
state: UUID of the state
estimate_point: Estimate point value
Returns:
Created Epic object
"""
🤖 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 89 - 112, The docstring for
create_epic is out of sync with the API: remove non-existent parameters
workspace_slug and type_id from the Args list, ensure the Args section exactly
lists the actual function parameters (e.g., project_id, name, assignees, labels,
point, description_html, description_stripped, priority, start_date,
target_date, sort_order, is_draft, external_source, external_id, parent, state,
estimate_point), and update the Returns section to indicate it returns an Epic
(not WorkItem); keep both Args and Returns sections present and concise to match
coding guidelines and the create_epic function signature.

Comment on lines +115 to +118
# Validate priority against allowed literal values
validated_priority: PriorityEnum | None = (
priority if priority in get_args(PriorityEnum) else None # type: ignore[assignment]
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject invalid priority instead of silently dropping it.

At Line 117, invalid priority values are coerced to None, which hides bad input and creates unexpected updates. create_epic should fail fast, like update_epic.

Suggested fix
-        # Validate priority against allowed literal values
-        validated_priority: PriorityEnum | None = (
-            priority if priority in get_args(PriorityEnum) else None  # type: ignore[assignment]
-        )
+        # Validate priority against allowed literal values
+        valid_priorities = get_args(PriorityEnum)
+        if priority is not None and priority not in valid_priorities:
+            raise ValueError(f"Invalid priority '{priority}'. Must be one of: {valid_priorities}")
+        validated_priority: PriorityEnum | None = priority  # type: ignore[assignment]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Validate priority against allowed literal values
validated_priority: PriorityEnum | None = (
priority if priority in get_args(PriorityEnum) else None # type: ignore[assignment]
)
# Validate priority against allowed literal values
valid_priorities = get_args(PriorityEnum)
if priority is not None and priority not in valid_priorities:
raise ValueError(f"Invalid priority '{priority}'. Must be one of: {valid_priorities}")
validated_priority: PriorityEnum | None = priority # type: ignore[assignment]
🤖 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 115 - 118, The code silently coerces
invalid priority values to None when constructing validated_priority; instead,
in create_epic validate the incoming priority against PriorityEnum (use
get_args(PriorityEnum) or PriorityEnum.__members__) and raise a clear exception
(e.g., ValueError) if the value is not allowed so the call fails fast; update
the logic around validated_priority in the create_epic implementation (and any
helper validating function) to perform this explicit check and raise rather than
assigning None.

Comment on lines +133 to +140
def delete_milestone(project_id: str, milestone_id: str) -> None:
"""
Delete a milestone by ID.

Args:
project_id: UUID of the project
milestone_id: UUID of the milestone
"""
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add Returns sections to these tool docstrings.

delete_milestone, add_work_items_to_milestone, and remove_work_items_from_milestone are missing a Returns section.

Suggested fix
     def delete_milestone(project_id: str, milestone_id: str) -> None:
         """
         Delete a milestone by ID.
@@
         Args:
             project_id: UUID of the project
             milestone_id: UUID of the milestone
+
+        Returns:
+            None
         """
@@
     def add_work_items_to_milestone(
@@
         Args:
             project_id: UUID of the project
             milestone_id: UUID of the milestone
             issue_ids: List of work item IDs to add to the milestone
+
+        Returns:
+            None
         """
@@
     def remove_work_items_from_milestone(
@@
         Args:
             project_id: UUID of the project
             milestone_id: UUID of the milestone
             issue_ids: List of work item IDs to remove from the milestone
+
+        Returns:
+            None
         """
As per coding guidelines: “`plane_mcp/tools/*.py`: Tool docstrings must include Args and Returns sections.”

Also applies to: 152-159, 174-181

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plane_mcp/tools/milestones.py` around lines 133 - 140, Update the docstrings
for delete_milestone, add_work_items_to_milestone, and
remove_work_items_from_milestone to include a Returns section; specifically, for
delete_milestone and the two modify functions add a "Returns:" entry that states
the return type (None) and a brief description such as "None — performs
operation or raises on failure" or "None on success." Edit the docstrings inside
the corresponding function definitions (delete_milestone,
add_work_items_to_milestone, remove_work_items_from_milestone) so they follow
the existing Args/Returns formatting used elsewhere in plane_mcp/tools/*.py.

Comment on lines +176 to +183
milestone_result = await client.call_tool(
"create_milestone",
{
"project_id": project_id,
"name": f"Milestone {unique_id}",
"description": "Integration test milestone",
"associated_work_item_ids": [epic_id, work_item_1_id, work_item_2_id],
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Milestone tool calls use invalid argument names and will fail at runtime.

At Line 180/210, this test sends name, but milestone tools expect title. It also sends unsupported description and associated_work_item_ids fields.

Suggested fix
         milestone_result = await client.call_tool(
             "create_milestone",
             {
                 "project_id": project_id,
-                "name": f"Milestone {unique_id}",
-                "description": "Integration test milestone",   
-                "associated_work_item_ids": [epic_id, work_item_1_id, work_item_2_id],
+                "title": f"Milestone {unique_id}",
             },
         )
@@
+        await client.call_tool(
+            "add_work_items_to_milestone",
+            {
+                "project_id": project_id,
+                "milestone_id": milestone_id,
+                "issue_ids": [epic_id, work_item_1_id, work_item_2_id],
+            },
+        )
@@
         await client.call_tool(
             "update_milestone", 
             { 
                 "project_id": project_id, 
                 "milestone_id": milestone_id, 
-                "name": f"Updated Milestone {unique_id}", 
-                "description": "Updated description for integration test milestone" 
+                "title": f"Updated Milestone {unique_id}",
             },
         ) 

Also applies to: 206-212

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_integration.py` around lines 176 - 183, The test is calling the
"create_milestone" tool with invalid argument names (sending name, description,
and associated_work_item_ids) which will fail; update the client.call_tool calls
that invoke "create_milestone" (e.g., where milestone_result is assigned) to use
the tool's expected arguments: replace "name" with "title", remove the
unsupported "description" and "associated_work_item_ids" keys, and supply only
the parameters the milestone tool accepts (keep "project_id" and any other
supported fields per the tool contract).

Comment on lines +362 to 368
# Epic tools
"list_epics",
"retrieve_epic",
"create_epic",
"update_epic",
"delete_epic",
]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add milestone tools to EXPECTED_TOOLS coverage.

The availability test currently validates epic tools but omits the newly added milestone tools, so registration regressions there won’t be caught.

Suggested fix
     # Epic tools
     "list_epics",
     "retrieve_epic",
     "create_epic",
     "update_epic",
     "delete_epic",
+    # Milestone tools
+    "list_milestones",
+    "create_milestone",
+    "retrieve_milestone",
+    "update_milestone",
+    "delete_milestone",
+    "add_work_items_to_milestone",
+    "remove_work_items_from_milestone",
+    "list_milestone_work_items",
 ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_integration.py` around lines 362 - 368, The test's EXPECTED_TOOLS
list omits the new milestone tools so availability checks miss regressions;
update the EXPECTED_TOOLS constant used in tests/test_integration.py (symbol
EXPECTED_TOOLS) to include the milestone tool names — e.g., "list_milestones",
"retrieve_milestone", "create_milestone", "update_milestone", and
"delete_milestone" — ensuring the test validates registration of
milestone-related tools alongside the epic tools.

@Prashant-Surya Prashant-Surya merged commit 73f9600 into makeplane:canary Mar 3, 2026
1 check passed
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.

3 participants