Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions plane_mcp/tools/cycles.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Cycle-related tools for Plane MCP Server."""

import json
from typing import Any

from fastmcp import FastMCP
Expand Down Expand Up @@ -207,6 +208,14 @@ def add_work_items_to_cycle(
cycle_id: UUID of the cycle
issue_ids: List of work item IDs to add to the cycle
"""
# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(issue_ids, str):
try:
issue_ids = json.loads(issue_ids)
except json.JSONDecodeError as e:
raise ValueError(
f"issue_ids must be a JSON array string or a list, got: {issue_ids!r}"
) from e
client, workspace_slug = get_plane_client_context()
client.cycles.add_work_items(
workspace_slug=workspace_slug,
Expand Down
37 changes: 36 additions & 1 deletion plane_mcp/tools/epics.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Epic-related tools for Plane MCP Server."""
import json
from typing import get_args

from fastmcp import FastMCP
Expand All @@ -18,7 +19,9 @@
def register_epic_tools(mcp: FastMCP) -> None:
"""Register all epic-related tools with the MCP server."""

def _get_epic_work_item_type(client: PlaneClient, workspace_slug: str, project_id: str) -> WorkItemType | None:
def _get_epic_work_item_type(
client: PlaneClient, workspace_slug: str, project_id: str
) -> WorkItemType | None:
"""Helper function to get the work item type ID for epics."""
response = client.work_item_types.list(
workspace_slug=workspace_slug,
Expand Down Expand Up @@ -122,6 +125,22 @@ def create_epic(
if epic_type is None:
raise ValueError("No work item type with is_epic=True found in the project")

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(assignees, str):
try:
assignees = json.loads(assignees)
except json.JSONDecodeError as e:
raise ValueError(
f"assignees must be a JSON array string or a list, got: {assignees!r}"
) from e
if isinstance(labels, str):
try:
labels = json.loads(labels)
except json.JSONDecodeError as e:
raise ValueError(
f"labels must be a JSON array string or a list, got: {labels!r}"
) from e

data = CreateWorkItem(
name=name,
assignees=assignees,
Expand Down Expand Up @@ -205,6 +224,22 @@ def update_epic(
raise ValueError(f"Invalid priority '{priority}'. Must be one of: {valid_priorities}")
validated_priority: PriorityEnum | None = priority # type: ignore[assignment]

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(assignees, str):
try:
assignees = json.loads(assignees)
except json.JSONDecodeError as e:
raise ValueError(
f"assignees must be a JSON array string or a list, got: {assignees!r}"
) from e
if isinstance(labels, str):
try:
labels = json.loads(labels)
except json.JSONDecodeError as e:
raise ValueError(
f"labels must be a JSON array string or a list, got: {labels!r}"
) from e

data = UpdateWorkItem(
name=name,
assignees=assignees,
Expand Down
17 changes: 17 additions & 0 deletions plane_mcp/tools/milestones.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Milestone-related tools for Plane MCP Server."""

import json
from typing import Any

from fastmcp import FastMCP
Expand Down Expand Up @@ -157,6 +158,14 @@ def add_work_items_to_milestone(
milestone_id: UUID of the milestone
issue_ids: List of work item IDs to add to the milestone
"""
# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(issue_ids, str):
try:
issue_ids = json.loads(issue_ids)
except json.JSONDecodeError as e:
raise ValueError(
f"issue_ids must be a JSON array string or a list, got: {issue_ids!r}"
) from e
client, workspace_slug = get_plane_client_context()
client.milestones.add_work_items(
workspace_slug=workspace_slug,
Expand All @@ -179,6 +188,14 @@ def remove_work_items_from_milestone(
milestone_id: UUID of the milestone
issue_ids: List of work item IDs to remove from the milestone
"""
# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(issue_ids, str):
try:
issue_ids = json.loads(issue_ids)
except json.JSONDecodeError as e:
raise ValueError(
f"issue_ids must be a JSON array string or a list, got: {issue_ids!r}"
) from e
client, workspace_slug = get_plane_client_context()
client.milestones.remove_work_items(
workspace_slug=workspace_slug,
Expand Down
27 changes: 27 additions & 0 deletions plane_mcp/tools/modules.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Module-related tools for Plane MCP Server."""

import json
from typing import Any, get_args

from fastmcp import FastMCP
Expand Down Expand Up @@ -81,6 +82,15 @@ def create_module(
status if status in get_args(ModuleStatusEnum) else None # type: ignore[assignment]
)

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(members, str):
try:
members = json.loads(members)
except json.JSONDecodeError as e:
raise ValueError(
f"members must be a JSON array string or a list, got: {members!r}"
) from e

data = CreateModule(
name=name,
description=description,
Expand Down Expand Up @@ -156,6 +166,15 @@ def update_module(
status if status in get_args(ModuleStatusEnum) else None # type: ignore[assignment]
)

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(members, str):
try:
members = json.loads(members)
except json.JSONDecodeError as e:
raise ValueError(
f"members must be a JSON array string or a list, got: {members!r}"
) from e

data = UpdateModule(
name=name,
description=description,
Expand Down Expand Up @@ -224,6 +243,14 @@ def add_work_items_to_module(
module_id: UUID of the module
issue_ids: List of work item IDs to add to the module
"""
# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(issue_ids, str):
try:
issue_ids = json.loads(issue_ids)
except json.JSONDecodeError as e:
raise ValueError(
f"issue_ids must be a JSON array string or a list, got: {issue_ids!r}"
) from e
client, workspace_slug = get_plane_client_context()
client.modules.add_work_items(
workspace_slug=workspace_slug,
Expand Down
31 changes: 27 additions & 4 deletions plane_mcp/tools/work_item_properties.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Work item property-related tools for Plane MCP Server."""

import json
from typing import Any

from fastmcp import FastMCP
Expand Down Expand Up @@ -74,14 +75,16 @@ def create_work_item_property(
project_id: UUID of the project
type_id: UUID of the work item type
display_name: Display name for the property
property_type: Type of property (TEXT, DATETIME, DECIMAL, BOOLEAN, OPTION, RELATION, URL, EMAIL, FILE)
property_type: Type of property
(TEXT, DATETIME, DECIMAL, BOOLEAN, OPTION, RELATION, URL, EMAIL, FILE)
relation_type: Relation type (ISSUE, USER) - required for RELATION properties
description: Property description
is_required: Whether the property is required
default_value: Default value(s) for the property
settings: Settings dictionary - required for TEXT and DATETIME properties
For TEXT: {"display_format": "single-line"|"multi-line"|"readonly"}
For DATETIME: {"display_format": "MMM dd, yyyy"|"dd/MM/yyyy"|"MM/dd/yyyy"|"yyyy/MM/dd"}
For DATETIME: {"display_format": "MMM dd, yyyy"|"dd/MM/yyyy"|"MM/dd/yyyy"|
"yyyy/MM/dd"}
is_active: Whether the property is active
is_multi: Whether the property supports multiple values
validation_rules: Validation rules dictionary
Expand Down Expand Up @@ -115,6 +118,15 @@ def create_work_item_property(
if options:
processed_options = [CreateWorkItemPropertyOption(**opt) for opt in options]

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(default_value, str):
try:
default_value = json.loads(default_value)
except json.JSONDecodeError as e:
raise ValueError(
f"default_value must be a JSON array string or a list, got: {default_value!r}"
) from e

data = CreateWorkItemProperty(
display_name=display_name,
property_type=validated_property_type,
Expand Down Expand Up @@ -188,14 +200,16 @@ def update_work_item_property(
type_id: UUID of the work item type
work_item_property_id: UUID of the property
display_name: Display name for the property
property_type: Type of property (TEXT, DATETIME, DECIMAL, BOOLEAN, OPTION, RELATION, URL, EMAIL, FILE)
property_type: Type of property
(TEXT, DATETIME, DECIMAL, BOOLEAN, OPTION, RELATION, URL, EMAIL, FILE)
relation_type: Relation type (ISSUE, USER) - required when updating to RELATION
description: Property description
is_required: Whether the property is required
default_value: Default value(s) for the property
settings: Settings dictionary - required when updating to TEXT or DATETIME
For TEXT: {"display_format": "single-line"|"multi-line"|"readonly"}
For DATETIME: {"display_format": "MMM dd, yyyy"|"dd/MM/yyyy"|"MM/dd/yyyy"|"yyyy/MM/dd"}
For DATETIME: {"display_format": "MMM dd, yyyy"|"dd/MM/yyyy"|"MM/dd/yyyy"|
"yyyy/MM/dd"}
is_active: Whether the property is active
is_multi: Whether the property supports multiple values
validation_rules: Validation rules dictionary
Expand Down Expand Up @@ -225,6 +239,15 @@ def update_work_item_property(
elif property_type == "DATETIME":
processed_settings = DateAttributeSettings(**settings)

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(default_value, str):
try:
default_value = json.loads(default_value)
except json.JSONDecodeError as e:
raise ValueError(
f"default_value must be a JSON array string or a list, got: {default_value!r}"
) from e

data = UpdateWorkItemProperty(
display_name=display_name,
property_type=validated_property_type,
Expand Down
10 changes: 10 additions & 0 deletions plane_mcp/tools/work_item_relations.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Work item relation-related tools for Plane MCP Server."""

import json
from typing import get_args

from fastmcp import FastMCP
Expand Down Expand Up @@ -73,6 +74,15 @@ def create_work_item_relation(
)
validated_relation_type: WorkItemRelationTypeEnum = relation_type # type: ignore[assignment]

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(issues, str):
try:
issues = json.loads(issues)
except json.JSONDecodeError as e:
raise ValueError(
f"issues must be a JSON array string or a list, got: {issues!r}"
) from e
Comment thread
coderabbitai[bot] marked this conversation as resolved.

data = CreateWorkItemRelation(
relation_type=validated_relation_type,
issues=issues,
Expand Down
19 changes: 19 additions & 0 deletions plane_mcp/tools/work_item_types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Work item type-related tools for Plane MCP Server."""

import json
from typing import Any

from fastmcp import FastMCP
Expand Down Expand Up @@ -64,6 +65,15 @@ def create_work_item_type(
"""
client, workspace_slug = get_plane_client_context()

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(project_ids, str):
try:
project_ids = json.loads(project_ids)
except json.JSONDecodeError as e:
raise ValueError(
f"project_ids must be a JSON array string or a list, got: {project_ids!r}"
) from e

data = CreateWorkItemType(
name=name,
description=description,
Expand Down Expand Up @@ -131,6 +141,15 @@ def update_work_item_type(
"""
client, workspace_slug = get_plane_client_context()

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(project_ids, str):
try:
project_ids = json.loads(project_ids)
except json.JSONDecodeError as e:
raise ValueError(
f"project_ids must be a JSON array string or a list, got: {project_ids!r}"
) from e

data = UpdateWorkItemType(
name=name,
description=description,
Expand Down
33 changes: 33 additions & 0 deletions plane_mcp/tools/work_items.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Work item-related tools for Plane MCP Server."""

import json
from typing import get_args

from fastmcp import FastMCP
Expand Down Expand Up @@ -125,6 +126,22 @@ def create_work_item(
priority if priority in get_args(PriorityEnum) else None # type: ignore[assignment]
)

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(assignees, str):
try:
assignees = json.loads(assignees)
except json.JSONDecodeError as e:
raise ValueError(
f"assignees must be a JSON array string or a list, got: {assignees!r}"
) from e
if isinstance(labels, str):
try:
labels = json.loads(labels)
except json.JSONDecodeError as e:
raise ValueError(
f"labels must be a JSON array string or a list, got: {labels!r}"
) from e

data = CreateWorkItem(
name=name,
assignees=assignees,
Expand Down Expand Up @@ -295,6 +312,22 @@ def update_work_item(
priority if priority in get_args(PriorityEnum) else None # type: ignore[assignment]
)

# Some MCP clients serialize list parameters as JSON strings; handle both cases
if isinstance(assignees, str):
try:
assignees = json.loads(assignees)
except json.JSONDecodeError as e:
raise ValueError(
f"assignees must be a JSON array string or a list, got: {assignees!r}"
) from e
if isinstance(labels, str):
try:
labels = json.loads(labels)
except json.JSONDecodeError as e:
raise ValueError(
f"labels must be a JSON array string or a list, got: {labels!r}"
) from e

data = UpdateWorkItem(
name=name,
assignees=assignees,
Expand Down
Loading