feat: add milestone tools for managing epics and milestones#64
feat: add milestone tools for managing epics and milestones#64Prashant-Surya merged 5 commits intomakeplane:canaryfrom
Conversation
📝 WalkthroughWalkthroughThis 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~40 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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 |
3816064 to
78dc820
Compare
|
@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 🙌 |
|
@Prashant-Surya thanks! Will do |
|
@Prashant-Surya ready for review! |
|
@jessedijkstra can you check the merge conflict. |
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>
6c10eac to
1f62847
Compare
|
@sriramveeraghanta updated, could you merge? |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
mise.tomlplane_mcp/tools/__init__.pyplane_mcp/tools/epics.pyplane_mcp/tools/milestones.pypyproject.tomltests/test_integration.py
| 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 | ||
| """ |
There was a problem hiding this comment.
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📝 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.
| 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.
| # Validate priority against allowed literal values | ||
| validated_priority: PriorityEnum | None = ( | ||
| priority if priority in get_args(PriorityEnum) else None # type: ignore[assignment] | ||
| ) |
There was a problem hiding this comment.
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.
| # 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.
| 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 | ||
| """ |
There was a problem hiding this comment.
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
"""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.
| 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], | ||
| }, |
There was a problem hiding this comment.
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).
| # Epic tools | ||
| "list_epics", | ||
| "retrieve_epic", | ||
| "create_epic", | ||
| "update_epic", | ||
| "delete_epic", | ||
| ] |
There was a problem hiding this comment.
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.
Summary
plane-sdkdependency to use themilestone-methodsbranchDependencies
plane-sdkdependency inpyproject.tomlpoints to that branch.Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores
Tests