diff --git a/plane_mcp/tools/__init__.py b/plane_mcp/tools/__init__.py index 62bc43c4..2b247cfd 100644 --- a/plane_mcp/tools/__init__.py +++ b/plane_mcp/tools/__init__.py @@ -13,12 +13,12 @@ from plane_mcp.tools.projects import register_project_tools from plane_mcp.tools.states import register_state_tools from plane_mcp.tools.users import register_user_tools -from plane_mcp.tools.work_item_activities import register_work_item_activity_tools from plane_mcp.tools.work_item_comments import register_work_item_comment_tools from plane_mcp.tools.work_item_links import register_work_item_link_tools from plane_mcp.tools.work_item_properties import register_work_item_property_tools from plane_mcp.tools.work_item_relations import register_work_item_relation_tools from plane_mcp.tools.work_item_types import register_work_item_type_tools +from plane_mcp.tools.unified import register_unified_tools from plane_mcp.tools.work_items import register_work_item_tools from plane_mcp.tools.work_logs import register_work_log_tools from plane_mcp.tools.workspaces import register_workspace_tools @@ -26,9 +26,9 @@ def register_tools(mcp: FastMCP) -> None: """Register all tools with the MCP server.""" + register_unified_tools(mcp) register_project_tools(mcp) register_work_item_tools(mcp) - register_work_item_activity_tools(mcp) register_work_item_comment_tools(mcp) register_work_item_link_tools(mcp) register_work_item_relation_tools(mcp) diff --git a/plane_mcp/tools/cycles.py b/plane_mcp/tools/cycles.py index a0814c3a..c23edb57 100644 --- a/plane_mcp/tools/cycles.py +++ b/plane_mcp/tools/cycles.py @@ -1,18 +1,12 @@ """Cycle-related tools for Plane MCP Server.""" -from typing import Any - from fastmcp import FastMCP from plane.models.cycles import ( CreateCycle, Cycle, - PaginatedArchivedCycleResponse, - PaginatedCycleResponse, - PaginatedCycleWorkItemResponse, TransferCycleWorkItemsRequest, UpdateCycle, ) -from plane.models.work_items import WorkItem from plane_mcp.client import get_plane_client_context @@ -20,28 +14,6 @@ def register_cycle_tools(mcp: FastMCP) -> None: """Register all cycle-related tools with the MCP server.""" - @mcp.tool() - def list_cycles( - project_id: str, - params: dict[str, Any] | None = None, - ) -> list[Cycle]: - """ - List all cycles in a project. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - params: Optional query parameters as a dictionary - - Returns: - List of Cycle objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedCycleResponse = client.cycles.list( - workspace_slug=workspace_slug, project_id=project_id, params=params - ) - return response.results - @mcp.tool() def create_cycle( project_id: str, @@ -88,22 +60,6 @@ def create_cycle( return client.cycles.create(workspace_slug=workspace_slug, project_id=project_id, data=data) - @mcp.tool() - def retrieve_cycle(project_id: str, cycle_id: str) -> Cycle: - """ - Retrieve a cycle by ID. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - cycle_id: UUID of the cycle - - Returns: - Cycle object - """ - client, workspace_slug = get_plane_client_context() - return client.cycles.retrieve(workspace_slug=workspace_slug, project_id=project_id, cycle_id=cycle_id) - @mcp.tool() def update_cycle( project_id: str, @@ -164,28 +120,6 @@ def delete_cycle(project_id: str, cycle_id: str) -> None: client, workspace_slug = get_plane_client_context() client.cycles.delete(workspace_slug=workspace_slug, project_id=project_id, cycle_id=cycle_id) - @mcp.tool() - def list_archived_cycles( - project_id: str, - params: dict[str, Any] | None = None, - ) -> list[Cycle]: - """ - List archived cycles in a project. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - params: Optional query parameters as a dictionary - - Returns: - List of archived Cycle objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedArchivedCycleResponse = client.cycles.list_archived( - workspace_slug=workspace_slug, project_id=project_id, params=params - ) - return response.results - @mcp.tool() def add_work_items_to_cycle( project_id: str, @@ -232,33 +166,6 @@ def remove_work_item_from_cycle( work_item_id=work_item_id, ) - @mcp.tool() - def list_cycle_work_items( - project_id: str, - cycle_id: str, - params: dict[str, Any] | None = None, - ) -> list[WorkItem]: - """ - List work items in a cycle. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - cycle_id: UUID of the cycle - params: Optional query parameters as a dictionary - - Returns: - List of WorkItem objects in the cycle - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedCycleWorkItemResponse = client.cycles.list_work_items( - workspace_slug=workspace_slug, - project_id=project_id, - cycle_id=cycle_id, - params=params, - ) - return response.results - @mcp.tool() def transfer_cycle_work_items( project_id: str, diff --git a/plane_mcp/tools/epics.py b/plane_mcp/tools/epics.py index 9385a170..990ed740 100644 --- a/plane_mcp/tools/epics.py +++ b/plane_mcp/tools/epics.py @@ -5,8 +5,7 @@ from fastmcp import FastMCP from plane import PlaneClient from plane.models.enums import PriorityEnum -from plane.models.epics import Epic, PaginatedEpicResponse -from plane.models.query_params import PaginatedQueryParams, RetrieveQueryParams +from plane.models.epics import Epic from plane.models.work_item_types import WorkItemType from plane.models.work_items import ( CreateWorkItem, @@ -32,38 +31,6 @@ def _get_epic_work_item_type(client: PlaneClient, workspace_slug: str, project_i return None - @mcp.tool() - def list_epics( - project_id: str, - cursor: str | None = None, - per_page: int | None = None, - ) -> list[Epic]: - """ - List all epics in a project. - - Args: - project_id: UUID of the project - cursor: Pagination cursor for getting next set of results - per_page: Number of results per page (1-100) - - Returns: - List of Epic objects - """ - client, workspace_slug = get_plane_client_context() - - params = PaginatedQueryParams( - cursor=cursor, - per_page=per_page, - ) - - response: PaginatedEpicResponse = client.epics.list( - workspace_slug=workspace_slug, - project_id=project_id, - params=params, - ) - - return response.results - @mcp.tool() def create_epic( project_id: str, @@ -235,32 +202,6 @@ def update_epic( epic_id=work_item.id, ) - @mcp.tool() - def retrieve_epic( - project_id: str, - epic_id: str, - ) -> Epic: - """ - Retrieve an epic by ID. - - Args: - project_id: UUID of the project - epic_id: UUID of the epic - - Returns: - Epic object - """ - client, workspace_slug = get_plane_client_context() - - params = RetrieveQueryParams() - - return client.epics.retrieve( - workspace_slug=workspace_slug, - project_id=project_id, - epic_id=epic_id, - params=params, - ) - @mcp.tool() def delete_epic( project_id: str, diff --git a/plane_mcp/tools/initiatives.py b/plane_mcp/tools/initiatives.py index 19483409..70946b7b 100644 --- a/plane_mcp/tools/initiatives.py +++ b/plane_mcp/tools/initiatives.py @@ -1,13 +1,10 @@ """Initiative-related tools for Plane MCP Server.""" -from typing import Any - from fastmcp import FastMCP from plane.models.enums import InitiativeState from plane.models.initiatives import ( CreateInitiative, Initiative, - PaginatedInitiativeResponse, UpdateInitiative, ) @@ -17,24 +14,6 @@ def register_initiative_tools(mcp: FastMCP) -> None: """Register all initiative-related tools with the MCP server.""" - @mcp.tool() - def list_initiatives( - params: dict[str, Any] | None = None, - ) -> list[Initiative]: - """ - List all initiatives in a workspace. - - Args: - workspace_slug: The workspace slug identifier - params: Optional query parameters as a dictionary (e.g., per_page, cursor) - - Returns: - List of Initiative objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedInitiativeResponse = client.initiatives.list(workspace_slug=workspace_slug, params=params) - return response.results - @mcp.tool() def create_initiative( name: str, @@ -75,21 +54,6 @@ def create_initiative( return client.initiatives.create(workspace_slug=workspace_slug, data=data) - @mcp.tool() - def retrieve_initiative(initiative_id: str) -> Initiative: - """ - Retrieve an initiative by ID. - - Args: - workspace_slug: The workspace slug identifier - initiative_id: UUID of the initiative - - Returns: - Initiative object - """ - client, workspace_slug = get_plane_client_context() - return client.initiatives.retrieve(workspace_slug=workspace_slug, initiative_id=initiative_id) - @mcp.tool() def update_initiative( initiative_id: str, diff --git a/plane_mcp/tools/intake.py b/plane_mcp/tools/intake.py index 0807f679..81b0b084 100644 --- a/plane_mcp/tools/intake.py +++ b/plane_mcp/tools/intake.py @@ -6,10 +6,8 @@ from plane.models.intake import ( CreateIntakeWorkItem, IntakeWorkItem, - PaginatedIntakeWorkItemResponse, UpdateIntakeWorkItem, ) -from plane.models.query_params import PaginatedQueryParams, RetrieveQueryParams from plane_mcp.client import get_plane_client_context @@ -17,33 +15,6 @@ def register_intake_tools(mcp: FastMCP) -> None: """Register all intake work item-related tools with the MCP server.""" - @mcp.tool() - def list_intake_work_items( - project_id: str, - params: dict[str, Any] | None = None, - ) -> list[IntakeWorkItem]: - """ - List all intake work items in a project. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - params: Optional query parameters as a dictionary (e.g., per_page, cursor) - - Returns: - List of IntakeWorkItem objects - """ - client, workspace_slug = get_plane_client_context() - - query_params = None - if params: - query_params = PaginatedQueryParams(**params) - - response: PaginatedIntakeWorkItemResponse = client.intake.list( - workspace_slug=workspace_slug, project_id=project_id, params=query_params - ) - return response.results - @mcp.tool() def create_intake_work_item( project_id: str, @@ -66,38 +37,6 @@ def create_intake_work_item( return client.intake.create(workspace_slug=workspace_slug, project_id=project_id, data=intake_data) - @mcp.tool() - def retrieve_intake_work_item( - project_id: str, - work_item_id: str, - params: dict[str, Any] | None = None, - ) -> IntakeWorkItem: - """ - Retrieve an intake work item by work item ID. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - work_item_id: UUID of the work item (use the issue field from - IntakeWorkItem response, not the intake work item ID) - params: Optional query parameters as a dictionary (e.g., expand, fields) - - Returns: - IntakeWorkItem object - """ - client, workspace_slug = get_plane_client_context() - - query_params = None - if params: - query_params = RetrieveQueryParams(**params) - - return client.intake.retrieve( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_id=work_item_id, - params=query_params, - ) - @mcp.tool() def update_intake_work_item( project_id: str, diff --git a/plane_mcp/tools/labels.py b/plane_mcp/tools/labels.py index 33921174..8882c090 100644 --- a/plane_mcp/tools/labels.py +++ b/plane_mcp/tools/labels.py @@ -1,12 +1,9 @@ """Label-related tools for Plane MCP Server.""" -from typing import Any - from fastmcp import FastMCP from plane.models.labels import ( CreateLabel, Label, - PaginatedLabelResponse, UpdateLabel, ) @@ -16,27 +13,6 @@ def register_label_tools(mcp: FastMCP) -> None: """Register all label-related tools with the MCP server.""" - @mcp.tool() - def list_labels( - project_id: str, - params: dict[str, Any] | None = None, - ) -> list[Label]: - """ - List all labels in a project. - - Args: - project_id: UUID of the project - params: Optional query parameters as a dictionary - - Returns: - List of Label objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedLabelResponse = client.labels.list( - workspace_slug=workspace_slug, project_id=project_id, params=params - ) - return response.results - @mcp.tool() def create_label( project_id: str, @@ -78,21 +54,6 @@ def create_label( return client.labels.create(workspace_slug=workspace_slug, project_id=project_id, data=data) - @mcp.tool() - def retrieve_label(project_id: str, label_id: str) -> Label: - """ - Retrieve a label by ID. - - Args: - project_id: UUID of the project - label_id: UUID of the label - - Returns: - Label object - """ - client, workspace_slug = get_plane_client_context() - return client.labels.retrieve(workspace_slug=workspace_slug, project_id=project_id, label_id=label_id) - @mcp.tool() def update_label( project_id: str, diff --git a/plane_mcp/tools/milestones.py b/plane_mcp/tools/milestones.py index 11870e5d..7824053c 100644 --- a/plane_mcp/tools/milestones.py +++ b/plane_mcp/tools/milestones.py @@ -6,9 +6,6 @@ from plane.models.milestones import ( CreateMilestone, Milestone, - MilestoneWorkItem, - PaginatedMilestoneResponse, - PaginatedMilestoneWorkItemResponse, UpdateMilestone, ) @@ -18,27 +15,6 @@ def register_milestone_tools(mcp: FastMCP) -> None: """Register all milestone-related tools with the MCP server.""" - @mcp.tool() - def list_milestones( - project_id: str, - params: dict[str, Any] | None = None, - ) -> list[Milestone]: - """ - List all milestones in a project. - - Args: - project_id: UUID of the project - params: Optional query parameters as a dictionary - - Returns: - List of Milestone objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedMilestoneResponse = client.milestones.list( - workspace_slug=workspace_slug, project_id=project_id, params=params - ) - return response.results - @mcp.tool() def create_milestone( project_id: str, @@ -71,23 +47,6 @@ def create_milestone( return client.milestones.create(workspace_slug=workspace_slug, project_id=project_id, data=data) - @mcp.tool() - def retrieve_milestone(project_id: str, milestone_id: str) -> Milestone: - """ - Retrieve a milestone by ID. - - Args: - project_id: UUID of the project - milestone_id: UUID of the milestone - - Returns: - Milestone object - """ - client, workspace_slug = get_plane_client_context() - return client.milestones.retrieve( - workspace_slug=workspace_slug, project_id=project_id, milestone_id=milestone_id - ) - @mcp.tool() def update_milestone( project_id: str, @@ -183,28 +142,3 @@ def remove_work_items_from_milestone( issue_ids=issue_ids, ) - @mcp.tool() - def list_milestone_work_items( - project_id: str, - milestone_id: str, - params: dict[str, Any] | None = None, - ) -> list[MilestoneWorkItem]: - """ - List work items in a milestone. - - Args: - project_id: UUID of the project - milestone_id: UUID of the milestone - params: Optional query parameters as a dictionary - - Returns: - List of MilestoneWorkItem objects in the milestone - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedMilestoneWorkItemResponse = client.milestones.list_work_items( - workspace_slug=workspace_slug, - project_id=project_id, - milestone_id=milestone_id, - params=params, - ) - return response.results diff --git a/plane_mcp/tools/modules.py b/plane_mcp/tools/modules.py index 4c75bacb..84d4b9a3 100644 --- a/plane_mcp/tools/modules.py +++ b/plane_mcp/tools/modules.py @@ -1,18 +1,14 @@ """Module-related tools for Plane MCP Server.""" -from typing import Any, get_args +from typing import get_args from fastmcp import FastMCP from plane.models.enums import ModuleStatusEnum from plane.models.modules import ( CreateModule, Module, - PaginatedArchivedModuleResponse, - PaginatedModuleResponse, - PaginatedModuleWorkItemResponse, UpdateModule, ) -from plane.models.work_items import WorkItem from plane_mcp.client import get_plane_client_context @@ -20,28 +16,6 @@ def register_module_tools(mcp: FastMCP) -> None: """Register all module-related tools with the MCP server.""" - @mcp.tool() - def list_modules( - project_id: str, - params: dict[str, Any] | None = None, - ) -> list[Module]: - """ - List all modules in a project. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - params: Optional query parameters as a dictionary - - Returns: - List of Module objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedModuleResponse = client.modules.list( - workspace_slug=workspace_slug, project_id=project_id, params=params - ) - return response.results - @mcp.tool() def create_module( project_id: str, @@ -95,22 +69,6 @@ def create_module( return client.modules.create(workspace_slug=workspace_slug, project_id=project_id, data=data) - @mcp.tool() - def retrieve_module(project_id: str, module_id: str) -> Module: - """ - Retrieve a module by ID. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - module_id: UUID of the module - - Returns: - Module object - """ - client, workspace_slug = get_plane_client_context() - return client.modules.retrieve(workspace_slug=workspace_slug, project_id=project_id, module_id=module_id) - @mcp.tool() def update_module( project_id: str, @@ -181,28 +139,6 @@ def delete_module(project_id: str, module_id: str) -> None: client, workspace_slug = get_plane_client_context() client.modules.delete(workspace_slug=workspace_slug, project_id=project_id, module_id=module_id) - @mcp.tool() - def list_archived_modules( - project_id: str, - params: dict[str, Any] | None = None, - ) -> list[Module]: - """ - List archived modules in a project. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - params: Optional query parameters as a dictionary - - Returns: - List of archived Module objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedArchivedModuleResponse = client.modules.list_archived( - workspace_slug=workspace_slug, project_id=project_id, params=params - ) - return response.results - @mcp.tool() def add_work_items_to_module( project_id: str, @@ -249,33 +185,6 @@ def remove_work_item_from_module( work_item_id=work_item_id, ) - @mcp.tool() - def list_module_work_items( - project_id: str, - module_id: str, - params: dict[str, Any] | None = None, - ) -> list[WorkItem]: - """ - List work items in a module. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - module_id: UUID of the module - params: Optional query parameters as a dictionary - - Returns: - List of WorkItem objects in the module - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedModuleWorkItemResponse = client.modules.list_work_items( - workspace_slug=workspace_slug, - project_id=project_id, - module_id=module_id, - params=params, - ) - return response.results - @mcp.tool() def archive_module(project_id: str, module_id: str) -> None: """ diff --git a/plane_mcp/tools/projects.py b/plane_mcp/tools/projects.py index 1c257e7b..106cba76 100644 --- a/plane_mcp/tools/projects.py +++ b/plane_mcp/tools/projects.py @@ -6,14 +6,11 @@ from plane.models.enums import TimezoneEnum from plane.models.projects import ( CreateProject, - PaginatedProjectResponse, Project, ProjectFeature, ProjectWorklogSummary, UpdateProject, ) -from plane.models.query_params import PaginatedQueryParams -from plane.models.users import UserLite from plane_mcp.client import get_plane_client_context @@ -21,45 +18,6 @@ def register_project_tools(mcp: FastMCP) -> None: """Register all project-related tools with the MCP server.""" - @mcp.tool() - def list_projects( - cursor: str | None = None, - per_page: int | None = None, - expand: str | None = None, - fields: str | None = None, - order_by: str | None = None, - ) -> list[Project]: - """ - List all projects in a workspace. - - Args: - workspace_slug: The workspace slug identifier - cursor: Pagination cursor for getting next set of results - per_page: Number of results per page (1-100) - expand: Comma-separated list of related fields to expand in response - fields: Comma-separated list of fields to include in response - order_by: Field to order results by. Prefix with '-' for descending order - - Returns: - List of Project objects - """ - client, workspace_slug = get_plane_client_context() - - params = PaginatedQueryParams( - cursor=cursor, - per_page=per_page, - expand=expand, - fields=fields, - order_by=order_by, - ) - - response: PaginatedProjectResponse = client.projects.list( - workspace_slug=workspace_slug, - params=params, - ) - - return response.results - @mcp.tool() def create_project( name: str, @@ -141,21 +99,6 @@ def create_project( return client.projects.create(workspace_slug=workspace_slug, data=data) - @mcp.tool() - def retrieve_project(project_id: str) -> Project: - """ - Retrieve a project by ID. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - - Returns: - Project object - """ - client, workspace_slug = get_plane_client_context() - return client.projects.retrieve(workspace_slug=workspace_slug, project_id=project_id) - @mcp.tool() def update_project( project_id: str, @@ -275,22 +218,6 @@ def get_project_worklog_summary(project_id: str) -> list[ProjectWorklogSummary]: client, workspace_slug = get_plane_client_context() return client.projects.get_worklog_summary(workspace_slug=workspace_slug, project_id=project_id) - @mcp.tool() - def get_project_members(project_id: str, params: dict[str, Any] | None = None) -> list[UserLite]: - """ - Get all members of a project. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - params: Optional query parameters as a dictionary - - Returns: - List of UserLite objects representing project members - """ - client, workspace_slug = get_plane_client_context() - return client.projects.get_members(workspace_slug=workspace_slug, project_id=project_id, params=params) - @mcp.tool() def get_project_features(project_id: str) -> ProjectFeature: """ diff --git a/plane_mcp/tools/states.py b/plane_mcp/tools/states.py index 53a48757..2468c65d 100644 --- a/plane_mcp/tools/states.py +++ b/plane_mcp/tools/states.py @@ -1,12 +1,11 @@ """State-related tools for Plane MCP Server.""" -from typing import Any, get_args +from typing import get_args from fastmcp import FastMCP from plane.models.enums import GroupEnum from plane.models.states import ( CreateState, - PaginatedStateResponse, State, UpdateState, ) @@ -17,27 +16,6 @@ def register_state_tools(mcp: FastMCP) -> None: """Register all state-related tools with the MCP server.""" - @mcp.tool() - def list_states( - project_id: str, - params: dict[str, Any] | None = None, - ) -> list[State]: - """ - List all states in a project. - - Args: - project_id: UUID of the project - params: Optional query parameters as a dictionary - - Returns: - List of State objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedStateResponse = client.states.list( - workspace_slug=workspace_slug, project_id=project_id, params=params - ) - return response.results - @mcp.tool() def create_state( project_id: str, @@ -90,21 +68,6 @@ def create_state( return client.states.create(workspace_slug=workspace_slug, project_id=project_id, data=data) - @mcp.tool() - def retrieve_state(project_id: str, state_id: str) -> State: - """ - Retrieve a state by ID. - - Args: - project_id: UUID of the project - state_id: UUID of the state - - Returns: - State object - """ - client, workspace_slug = get_plane_client_context() - return client.states.retrieve(workspace_slug=workspace_slug, project_id=project_id, state_id=state_id) - @mcp.tool() def update_state( project_id: str, diff --git a/plane_mcp/tools/unified.py b/plane_mcp/tools/unified.py new file mode 100644 index 00000000..ab54df31 --- /dev/null +++ b/plane_mcp/tools/unified.py @@ -0,0 +1,300 @@ +"""Unified list and retrieve tools for Plane MCP Server.""" + +from typing import Any, Literal + +from fastmcp import FastMCP + +from plane_mcp.client import get_plane_client_context + +# Config format: entity_type -> (client_attr_path, method, container_id_kwarg, scope) +# scope: "workspace" | "project" | "cycle" | "module" | "milestone" | "work_item" | "work_item_type" +# container_id_kwarg: the kwarg name for the container ID (cycle_id, module_id, etc.), or None if not needed +ENTITY_LIST_CONFIG: dict[str, tuple[str, str, str | None, str]] = { + "projects": ("projects", "list", None, "workspace"), + "initiatives": ("initiatives", "list", None, "workspace"), + "workspace_members": ("workspaces", "get_members", None, "workspace"), + "work_items": ("work_items", "list", None, "project"), + "epics": ("epics", "list", None, "project"), + "cycles": ("cycles", "list", None, "project"), + "archived_cycles": ("cycles", "list_archived", None, "project"), + "modules": ("modules", "list", None, "project"), + "archived_modules": ("modules", "list_archived", None, "project"), + "labels": ("labels", "list", None, "project"), + "states": ("states", "list", None, "project"), + "intake_work_items": ("intake", "list", None, "project"), + "work_item_types": ("work_item_types", "list", None, "project"), + "milestones": ("milestones", "list", None, "project"), + "project_members": ("projects", "get_members", None, "project"), + "cycle_work_items": ("cycles", "list_work_items", "cycle_id", "cycle"), + "module_work_items": ("modules", "list_work_items", "module_id", "module"), + "milestone_work_items": ("milestones", "list_work_items", "milestone_id", "milestone"), + "work_item_activities": ("work_items.activities", "list", "work_item_id", "work_item"), + "work_item_comments": ("work_items.comments", "list", "work_item_id", "work_item"), + "work_item_links": ("work_items.links", "list", "work_item_id", "work_item"), + "work_item_relations": ("work_items.relations", "list", "work_item_id", "work_item"), + "work_logs": ("work_items.work_logs", "list", "work_item_id", "work_item"), + "work_item_properties": ("work_item_properties", "list", "type_id", "work_item_type"), +} + +# Config format: entity_type -> (client_attr_path, method, entity_id_kwarg, scope) +# entity_id_kwarg: the kwarg name for entity_id (e.g. "cycle_id"), or None for special cases +ENTITY_RETRIEVE_CONFIG: dict[str, tuple[str, str, str | None, str]] = { + "project": ("projects", "retrieve", "project_id", "workspace"), + "initiative": ("initiatives", "retrieve", "initiative_id", "workspace"), + "workspace_page": ("pages", "retrieve_workspace_page", "page_id", "workspace"), + "work_item": ("work_items", "retrieve", "work_item_id", "project"), + "work_item_by_identifier": ("work_items", "retrieve_by_identifier", None, "identifier"), + "epic": ("epics", "retrieve", "epic_id", "project"), + "cycle": ("cycles", "retrieve", "cycle_id", "project"), + "module": ("modules", "retrieve", "module_id", "project"), + "label": ("labels", "retrieve", "label_id", "project"), + "state": ("states", "retrieve", "state_id", "project"), + "intake_work_item": ("intake", "retrieve", "work_item_id", "project"), + "work_item_type": ("work_item_types", "retrieve", "work_item_type_id", "project"), + "milestone": ("milestones", "retrieve", "milestone_id", "project"), + "project_page": ("pages", "retrieve_project_page", "page_id", "project"), + "work_item_activity": ("work_items.activities", "retrieve", "activity_id", "work_item"), + "work_item_comment": ("work_items.comments", "retrieve", "comment_id", "work_item"), + "work_item_link": ("work_items.links", "retrieve", "link_id", "work_item"), + "work_item_property": ("work_item_properties", "retrieve", "work_item_property_id", "work_item_type"), +} + +# These methods don't accept a `params` kwarg +_NO_PARAMS_METHODS = { + "workspaces.get_members", + "projects.get_members", + "work_items.relations.list", +} + +# These methods return a list directly (no .results attribute) +_DIRECT_RETURN_METHODS = { + "work_item_types.list", + "work_item_properties.list", + "workspaces.get_members", + "projects.get_members", + "work_items.relations.list", + "work_items.work_logs.list", +} + + +def _resolve_client_attr(client: Any, attr_path: str) -> Any: + """Walk a dotted attribute path on the client object.""" + obj = client + for part in attr_path.split("."): + obj = getattr(obj, part) + return obj + + +def register_unified_tools(mcp: FastMCP) -> None: + """Register unified list and retrieve tools with the MCP server.""" + + @mcp.tool() + def entity_list( + entity_type: Literal[ + "projects", + "initiatives", + "workspace_members", + "work_items", + "epics", + "cycles", + "archived_cycles", + "modules", + "archived_modules", + "labels", + "states", + "intake_work_items", + "work_item_types", + "milestones", + "project_members", + "cycle_work_items", + "module_work_items", + "milestone_work_items", + "work_item_activities", + "work_item_comments", + "work_item_links", + "work_item_relations", + "work_logs", + "work_item_properties", + ], + project_id: str | None = None, + cycle_id: str | None = None, + module_id: str | None = None, + milestone_id: str | None = None, + work_item_id: str | None = None, + type_id: str | None = None, + params: dict[str, Any] | None = None, + ) -> list[Any]: + """ + List entities of any supported type in Plane. + + Use this tool to retrieve collections of Plane entities. The required scope + parameters depend on the entity_type: + - Workspace-scoped (no extra IDs needed): projects, initiatives, workspace_members + - Project-scoped (project_id required): work_items, epics, cycles, archived_cycles, + modules, archived_modules, labels, states, intake_work_items, work_item_types, + milestones, project_members + - Cycle-scoped (project_id + cycle_id required): cycle_work_items + - Module-scoped (project_id + module_id required): module_work_items + - Milestone-scoped (project_id + milestone_id required): milestone_work_items + - Work item-scoped (project_id + work_item_id required): work_item_activities, + work_item_comments, work_item_links, work_item_relations, work_logs + - Work item type-scoped (project_id + type_id required): work_item_properties + + Args: + entity_type: The type of entity to list + project_id: UUID of the project (required for project-scoped and deeper entities) + cycle_id: UUID of the cycle (required for cycle_work_items) + module_id: UUID of the module (required for module_work_items) + milestone_id: UUID of the milestone (required for milestone_work_items) + work_item_id: UUID of the work item (required for work item-scoped entities) + type_id: UUID of the work item type (required for work_item_properties) + params: Optional pagination/ordering parameters only. Supported keys: per_page (int), + cursor (str), order_by (str), expand (str), fields (str). Do NOT use this for + filtering (e.g. by assignee, state, label) — filtering is not supported by this tool. + + Returns: + List of entity objects of the requested type + """ + client, workspace_slug = get_plane_client_context() + + attr_path, method_name, container_id_kwarg, scope = ENTITY_LIST_CONFIG[entity_type] + method_key = f"{attr_path}.{method_name}" + + # Validate required scope params + if scope in ("project", "cycle", "module", "milestone", "work_item", "work_item_type"): + if project_id is None: + raise ValueError(f"project_id is required for entity_type='{entity_type}'") + if scope == "cycle" and cycle_id is None: + raise ValueError(f"cycle_id is required for entity_type='{entity_type}'") + if scope == "module" and module_id is None: + raise ValueError(f"module_id is required for entity_type='{entity_type}'") + if scope == "milestone" and milestone_id is None: + raise ValueError(f"milestone_id is required for entity_type='{entity_type}'") + if scope == "work_item" and work_item_id is None: + raise ValueError(f"work_item_id is required for entity_type='{entity_type}'") + if scope == "work_item_type" and type_id is None: + raise ValueError(f"type_id is required for entity_type='{entity_type}'") + + resource = _resolve_client_attr(client, attr_path) + method = getattr(resource, method_name) + + # Build kwargs + kwargs: dict[str, Any] = {"workspace_slug": workspace_slug} + + if scope in ("project", "cycle", "module", "milestone", "work_item", "work_item_type"): + kwargs["project_id"] = project_id + if container_id_kwarg is not None: + container_id_values: dict[str, str | None] = { + "cycle_id": cycle_id, + "module_id": module_id, + "milestone_id": milestone_id, + "work_item_id": work_item_id, + "type_id": type_id, + } + kwargs[container_id_kwarg] = container_id_values[container_id_kwarg] + + if method_key not in _NO_PARAMS_METHODS: + kwargs["params"] = params + + result = method(**kwargs) + + if method_key in _DIRECT_RETURN_METHODS: + return result + return result.results + + @mcp.tool() + def entity_retrieve( + entity_type: Literal[ + "project", + "initiative", + "workspace_page", + "work_item", + "work_item_by_identifier", + "epic", + "cycle", + "module", + "label", + "state", + "intake_work_item", + "work_item_type", + "milestone", + "project_page", + "work_item_activity", + "work_item_comment", + "work_item_link", + "work_item_property", + ], + entity_id: str | None = None, + project_id: str | None = None, + work_item_id: str | None = None, + type_id: str | None = None, + project_identifier: str | None = None, + issue_identifier: int | None = None, + ) -> Any: + """ + Retrieve a single entity by ID in Plane. + + Use this tool to fetch a specific Plane entity by its ID. The required parameters + depend on the entity_type: + - Workspace-scoped (entity_id required): project, initiative, workspace_page + - Project-scoped (project_id + entity_id required): work_item, epic, cycle, module, + label, state, intake_work_item, work_item_type, milestone, project_page + - Work item-scoped (project_id + work_item_id + entity_id required): + work_item_activity, work_item_comment, work_item_link + - Work item type-scoped (project_id + type_id + entity_id required): work_item_property + - Special: work_item_by_identifier requires project_identifier + issue_identifier + instead of entity_id + + Args: + entity_type: The type of entity to retrieve + entity_id: UUID of the entity to retrieve (not used for work_item_by_identifier) + project_id: UUID of the project (required for project-scoped and deeper entities) + work_item_id: UUID of the work item (required for work item-scoped entities) + type_id: UUID of the work item type (required for work_item_property) + project_identifier: Project identifier string, e.g. "MP" (for work_item_by_identifier) + issue_identifier: Issue sequence number, e.g. 42 (for work_item_by_identifier) + + Returns: + The requested entity object + """ + client, workspace_slug = get_plane_client_context() + + attr_path, method_name, entity_id_kwarg, scope = ENTITY_RETRIEVE_CONFIG[entity_type] + + resource = _resolve_client_attr(client, attr_path) + method = getattr(resource, method_name) + + # Special case: work_item_by_identifier + if entity_type == "work_item_by_identifier": + if project_identifier is None or issue_identifier is None: + raise ValueError("project_identifier and issue_identifier are required for work_item_by_identifier") + return method( + workspace_slug=workspace_slug, + project_identifier=project_identifier, + issue_identifier=issue_identifier, + ) + + # Validate required params + if entity_id is None: + raise ValueError(f"entity_id is required for entity_type='{entity_type}'") + if scope in ("project", "work_item", "work_item_type"): + if project_id is None: + raise ValueError(f"project_id is required for entity_type='{entity_type}'") + if scope == "work_item" and work_item_id is None: + raise ValueError(f"work_item_id is required for entity_type='{entity_type}'") + if scope == "work_item_type" and type_id is None: + raise ValueError(f"type_id is required for entity_type='{entity_type}'") + + # Build kwargs + kwargs: dict[str, Any] = {"workspace_slug": workspace_slug} + + if scope in ("project", "work_item", "work_item_type"): + kwargs["project_id"] = project_id + if scope == "work_item": + kwargs["work_item_id"] = work_item_id + if scope == "work_item_type": + kwargs["type_id"] = type_id + + kwargs[entity_id_kwarg] = entity_id + + return method(**kwargs) diff --git a/plane_mcp/tools/work_item_activities.py b/plane_mcp/tools/work_item_activities.py deleted file mode 100644 index 11a0d44f..00000000 --- a/plane_mcp/tools/work_item_activities.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Work item activity-related tools for Plane MCP Server.""" - -from typing import Any - -from fastmcp import FastMCP -from plane.models.work_items import ( - PaginatedWorkItemActivityResponse, - WorkItemActivity, -) - -from plane_mcp.client import get_plane_client_context - - -def register_work_item_activity_tools(mcp: FastMCP) -> None: - """Register all work item activity-related tools with the MCP server.""" - - @mcp.tool() - def list_work_item_activities( - project_id: str, - work_item_id: str, - params: dict[str, Any] | None = None, - ) -> list[WorkItemActivity]: - """ - List activities for a work item. - - Args: - project_id: UUID of the project - work_item_id: UUID of the work item - params: Optional query parameters as a dictionary - - Returns: - List of WorkItemActivity objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedWorkItemActivityResponse = client.work_items.activities.list( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_id=work_item_id, - params=params, - ) - return response.results - - @mcp.tool() - def retrieve_work_item_activity( - project_id: str, - work_item_id: str, - activity_id: str, - ) -> WorkItemActivity: - """ - Retrieve a specific activity for a work item. - - Args: - project_id: UUID of the project - work_item_id: UUID of the work item - activity_id: UUID of the activity - - Returns: - WorkItemActivity object - """ - client, workspace_slug = get_plane_client_context() - return client.work_items.activities.retrieve( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_id=work_item_id, - activity_id=activity_id, - ) diff --git a/plane_mcp/tools/work_item_comments.py b/plane_mcp/tools/work_item_comments.py index 7a8624ef..d8aa278e 100644 --- a/plane_mcp/tools/work_item_comments.py +++ b/plane_mcp/tools/work_item_comments.py @@ -6,7 +6,6 @@ from plane.models.enums import AccessEnum from plane.models.work_items import ( CreateWorkItemComment, - PaginatedWorkItemCommentResponse, UpdateWorkItemComment, WorkItemComment, ) @@ -17,57 +16,6 @@ def register_work_item_comment_tools(mcp: FastMCP) -> None: """Register all work item comment-related tools with the MCP server.""" - @mcp.tool() - def list_work_item_comments( - project_id: str, - work_item_id: str, - params: dict[str, Any] | None = None, - ) -> list[WorkItemComment]: - """ - List comments for a work item. - - Args: - project_id: UUID of the project - work_item_id: UUID of the work item - params: Optional query parameters as a dictionary - - Returns: - List of WorkItemComment objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedWorkItemCommentResponse = client.work_items.comments.list( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_id=work_item_id, - params=params, - ) - return response.results - - @mcp.tool() - def retrieve_work_item_comment( - project_id: str, - work_item_id: str, - comment_id: str, - ) -> WorkItemComment: - """ - Retrieve a specific comment for a work item. - - Args: - project_id: UUID of the project - work_item_id: UUID of the work item - comment_id: UUID of the comment - - Returns: - WorkItemComment object - """ - client, workspace_slug = get_plane_client_context() - return client.work_items.comments.retrieve( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_id=work_item_id, - comment_id=comment_id, - ) - @mcp.tool() def create_work_item_comment( project_id: str, diff --git a/plane_mcp/tools/work_item_links.py b/plane_mcp/tools/work_item_links.py index 24aa3c70..c8f1175e 100644 --- a/plane_mcp/tools/work_item_links.py +++ b/plane_mcp/tools/work_item_links.py @@ -1,11 +1,8 @@ """Work item link-related tools for Plane MCP Server.""" -from typing import Any - from fastmcp import FastMCP from plane.models.work_items import ( CreateWorkItemLink, - PaginatedWorkItemLinkResponse, UpdateWorkItemLink, WorkItemLink, ) @@ -16,57 +13,6 @@ def register_work_item_link_tools(mcp: FastMCP) -> None: """Register all work item link-related tools with the MCP server.""" - @mcp.tool() - def list_work_item_links( - project_id: str, - work_item_id: str, - params: dict[str, Any] | None = None, - ) -> list[WorkItemLink]: - """ - List links for a work item. - - Args: - project_id: UUID of the project - work_item_id: UUID of the work item - params: Optional query parameters as a dictionary - - Returns: - List of WorkItemLink objects - """ - client, workspace_slug = get_plane_client_context() - response: PaginatedWorkItemLinkResponse = client.work_items.links.list( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_id=work_item_id, - params=params, - ) - return response.results - - @mcp.tool() - def retrieve_work_item_link( - project_id: str, - work_item_id: str, - link_id: str, - ) -> WorkItemLink: - """ - Retrieve a specific link for a work item. - - Args: - project_id: UUID of the project - work_item_id: UUID of the work item - link_id: UUID of the link - - Returns: - WorkItemLink object - """ - client, workspace_slug = get_plane_client_context() - return client.work_items.links.retrieve( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_id=work_item_id, - link_id=link_id, - ) - @mcp.tool() def create_work_item_link( project_id: str, diff --git a/plane_mcp/tools/work_item_properties.py b/plane_mcp/tools/work_item_properties.py index 5ae998a0..4743d702 100644 --- a/plane_mcp/tools/work_item_properties.py +++ b/plane_mcp/tools/work_item_properties.py @@ -22,32 +22,6 @@ def register_work_item_property_tools(mcp: FastMCP) -> None: """Register all work item property-related tools with the MCP server.""" - @mcp.tool() - def list_work_item_properties( - project_id: str, - type_id: str, - params: dict[str, Any] | None = None, - ) -> list[WorkItemProperty]: - """ - List work item properties for a work item type. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - type_id: UUID of the work item type - params: Optional query parameters as a dictionary - - Returns: - List of WorkItemProperty objects - """ - client, workspace_slug = get_plane_client_context() - return client.work_item_properties.list( - workspace_slug=workspace_slug, - project_id=project_id, - type_id=type_id, - params=params, - ) - @mcp.tool() def create_work_item_property( project_id: str, @@ -137,32 +111,6 @@ def create_work_item_property( workspace_slug=workspace_slug, project_id=project_id, type_id=type_id, data=data ) - @mcp.tool() - def retrieve_work_item_property( - project_id: str, - type_id: str, - work_item_property_id: str, - ) -> WorkItemProperty: - """ - Retrieve a work item property by ID. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - type_id: UUID of the work item type - work_item_property_id: UUID of the property - - Returns: - WorkItemProperty object - """ - client, workspace_slug = get_plane_client_context() - return client.work_item_properties.retrieve( - workspace_slug=workspace_slug, - project_id=project_id, - type_id=type_id, - work_item_property_id=work_item_property_id, - ) - @mcp.tool() def update_work_item_property( project_id: str, diff --git a/plane_mcp/tools/work_item_relations.py b/plane_mcp/tools/work_item_relations.py index 361da90a..70f1f8dc 100644 --- a/plane_mcp/tools/work_item_relations.py +++ b/plane_mcp/tools/work_item_relations.py @@ -7,7 +7,6 @@ from plane.models.work_items import ( CreateWorkItemRelation, RemoveWorkItemRelation, - WorkItemRelationResponse, ) from plane_mcp.client import get_plane_client_context @@ -16,36 +15,6 @@ def register_work_item_relation_tools(mcp: FastMCP) -> None: """Register all work item relation-related tools with the MCP server.""" - @mcp.tool() - def list_work_item_relations( - project_id: str, - work_item_id: str, - ) -> WorkItemRelationResponse: - """ - List relations for a work item. - - Args: - project_id: UUID of the project - work_item_id: UUID of the work item - - Returns: - WorkItemRelationResponse containing lists of related work items by relation type: - - blocking: Work items that are blocking this item - - blocked_by: Work items that this item is blocked by - - duplicate: Work items that are duplicates of this item - - relates_to: Work items that relate to this item - - start_after: Work items that start after this item - - start_before: Work items that start before this item - - finish_after: Work items that finish after this item - - finish_before: Work items that finish before this item - """ - client, workspace_slug = get_plane_client_context() - return client.work_items.relations.list( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_id=work_item_id, - ) - @mcp.tool() def create_work_item_relation( project_id: str, diff --git a/plane_mcp/tools/work_item_types.py b/plane_mcp/tools/work_item_types.py index 31e4868b..a8f38906 100644 --- a/plane_mcp/tools/work_item_types.py +++ b/plane_mcp/tools/work_item_types.py @@ -15,24 +15,6 @@ def register_work_item_type_tools(mcp: FastMCP) -> None: """Register all work item type-related tools with the MCP server.""" - @mcp.tool() - def list_work_item_types( - project_id: str, - params: dict[str, Any] | None = None, - ) -> list[WorkItemType]: - """ - List all work item types in a project. - - Args: - project_id: UUID of the project - params: Optional query parameters as a dictionary - - Returns: - List of WorkItemType objects - """ - client, workspace_slug = get_plane_client_context() - return client.work_item_types.list(workspace_slug=workspace_slug, project_id=project_id, params=params) - @mcp.tool() def create_work_item_type( project_id: str, @@ -74,28 +56,6 @@ def create_work_item_type( return client.work_item_types.create(workspace_slug=workspace_slug, project_id=project_id, data=data) - @mcp.tool() - def retrieve_work_item_type( - project_id: str, - work_item_type_id: str, - ) -> WorkItemType: - """ - Retrieve a work item type by ID. - - Args: - project_id: UUID of the project - work_item_type_id: UUID of the work item type - - Returns: - WorkItemType object - """ - client, workspace_slug = get_plane_client_context() - return client.work_item_types.retrieve( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_type_id=work_item_type_id, - ) - @mcp.tool() def update_work_item_type( project_id: str, diff --git a/plane_mcp/tools/work_items.py b/plane_mcp/tools/work_items.py index 6809e247..520d1401 100644 --- a/plane_mcp/tools/work_items.py +++ b/plane_mcp/tools/work_items.py @@ -4,15 +4,13 @@ from fastmcp import FastMCP from plane.models.enums import PriorityEnum -from plane.models.query_params import RetrieveQueryParams, WorkItemQueryParams +from plane.models.query_params import RetrieveQueryParams from plane.models.work_items import ( AdvancedSearchResult, AdvancedSearchWorkItem, CreateWorkItem, - PaginatedWorkItemResponse, UpdateWorkItem, WorkItem, - WorkItemDetail, WorkItemSearch, ) @@ -64,118 +62,6 @@ def _build_advanced_search_filters( def register_work_item_tools(mcp: FastMCP) -> None: """Register all work item-related tools with the MCP server.""" - @mcp.tool() - def list_work_items( - project_id: str | None = None, - query: str | None = None, - assignee_ids: list[str] | None = None, - state_ids: list[str] | None = None, - state_groups: list[str] | None = None, - priorities: list[str] | None = None, - label_ids: list[str] | None = None, - type_ids: list[str] | None = None, - cycle_ids: list[str] | None = None, - module_ids: list[str] | None = None, - is_archived: bool | None = None, - created_by_ids: list[str] | None = None, - workspace_search: bool = False, - limit: int | None = None, - cursor: str | None = None, - per_page: int | None = None, - expand: str | None = None, - fields: str | None = None, - order_by: str | None = None, - external_id: str | None = None, - external_source: str | None = None, - ) -> list[WorkItem] | list[AdvancedSearchResult]: - """ - List work items in a project or search across the workspace. - - When any filter parameter is provided (assignee_ids, state_ids, state_groups, - priorities, label_ids, type_ids, cycle_ids, module_ids, is_archived, - created_by_ids, or query), this uses the advanced search endpoint which - supports powerful filtering. Otherwise it uses the standard list endpoint. - - Args: - project_id: UUID of the project. Required when no filters are provided. - Optional when using filters (omit for workspace-wide search). - query: Free-form text search across work item name and description - assignee_ids: List of user UUIDs to filter by assignee - state_ids: List of state UUIDs to filter by state - state_groups: List of state groups to filter by - (backlog, unstarted, started, completed, cancelled) - priorities: List of priority values to filter by - (urgent, high, medium, low, none) - label_ids: List of label UUIDs to filter by label - type_ids: List of work item type UUIDs to filter by type - cycle_ids: List of cycle UUIDs to filter by cycle - module_ids: List of module UUIDs to filter by module - is_archived: Filter by archived status (true/false) - created_by_ids: List of user UUIDs to filter by creator - workspace_search: When true, search across all projects in the workspace. - Only used with filters. Defaults to false. - limit: Maximum number of results (only used with filters, default 25) - cursor: Pagination cursor for getting next set of results (list only) - per_page: Number of results per page, 1-100 (list only) - expand: Comma-separated list of related fields to expand in response - (list only, e.g. "assignees,labels,state") - fields: Comma-separated list of fields to include in response (list only) - order_by: Field to order results by, prefix with '-' for descending (list only) - external_id: External system identifier for filtering (list only) - external_source: External system source name for filtering (list only) - - Returns: - List of WorkItem objects (unfiltered) or AdvancedSearchResult objects (filtered) - """ - client, workspace_slug = get_plane_client_context() - - filters = _build_advanced_search_filters( - assignee_ids=assignee_ids, - state_ids=state_ids, - state_groups=state_groups, - priorities=priorities, - label_ids=label_ids, - type_ids=type_ids, - cycle_ids=cycle_ids, - module_ids=module_ids, - is_archived=is_archived, - created_by_ids=created_by_ids, - ) - - if filters is not None or query is not None: - data = AdvancedSearchWorkItem( - query=query, - filters=filters, - limit=limit, - project_id=project_id, - workspace_search=workspace_search or None, - ) - return client.work_items.advanced_search( - workspace_slug=workspace_slug, - data=data, - ) - - if project_id is None: - raise ValueError("project_id is required when no filters are provided") - - params = WorkItemQueryParams( - cursor=cursor, - per_page=per_page, - expand=expand, - fields=fields, - order_by=order_by, - external_id=external_id, - external_source=external_source, - ) - - response: PaginatedWorkItemResponse = client.work_items.list( - workspace_slug=workspace_slug, - project_id=project_id, - params=params, - ) - - return response.results - @mcp.tool() def create_work_item( project_id: str, @@ -256,92 +142,6 @@ def create_work_item( return client.work_items.create(workspace_slug=workspace_slug, project_id=project_id, data=data) - @mcp.tool() - def retrieve_work_item( - project_id: str, - work_item_id: str, - expand: str | None = None, - fields: str | None = None, - external_id: str | None = None, - external_source: str | None = None, - order_by: str | None = None, - ) -> WorkItemDetail: - """ - Retrieve a work item by ID. - - Args: - workspace_slug: The workspace slug identifier - project_id: UUID of the project - work_item_id: UUID of the work item - expand: Comma-separated fields to expand (e.g., "assignees,labels,state") - fields: Comma-separated fields to include in response - external_id: External system identifier for filtering - external_source: External system source name for filtering - order_by: Field to order results by (typically not used for single item retrieval) - - Returns: - WorkItemDetail object with expanded relationships - """ - client, workspace_slug = get_plane_client_context() - - params = RetrieveQueryParams( - expand=expand, - fields=fields, - external_id=external_id, - external_source=external_source, - order_by=order_by, - ) - - return client.work_items.retrieve( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_id=work_item_id, - params=params, - ) - - @mcp.tool() - def retrieve_work_item_by_identifier( - project_identifier: str, - issue_identifier: int, - expand: str | None = None, - fields: str | None = None, - external_id: str | None = None, - external_source: str | None = None, - order_by: str | None = None, - ) -> WorkItemDetail: - """ - Retrieve a work item by project identifier and issue sequence number. - - Args: - workspace_slug: The workspace slug identifier - project_identifier: Project identifier string (e.g., "MP" for "My Project") - issue_identifier: Issue sequence number (e.g., 1, 2, 3) - expand: Comma-separated fields to expand (e.g., "assignees,labels,state") - fields: Comma-separated list of fields to include in response - external_id: External system identifier for filtering - external_source: External system source name for filtering - order_by: Field to order results by (typically not used for single item retrieval) - - Returns: - WorkItemDetail object with expanded relationships - """ - client, workspace_slug = get_plane_client_context() - - params = RetrieveQueryParams( - expand=expand, - fields=fields, - external_id=external_id, - external_source=external_source, - order_by=order_by, - ) - - return client.work_items.retrieve_by_identifier( - workspace_slug=workspace_slug, - project_identifier=project_identifier, - issue_identifier=issue_identifier, - params=params, - ) - @mcp.tool() def update_work_item( project_id: str, diff --git a/plane_mcp/tools/work_logs.py b/plane_mcp/tools/work_logs.py index c72efdb3..12badbd1 100644 --- a/plane_mcp/tools/work_logs.py +++ b/plane_mcp/tools/work_logs.py @@ -11,31 +11,6 @@ def register_work_log_tools(mcp: FastMCP) -> None: """Register all work log-related tools with the MCP server.""" - @mcp.tool() - def list_work_logs( - project_id: str, - work_item_id: str, - params: dict[str, Any] | None = None, - ) -> list[WorkItemWorkLog]: - """ - List work logs for a work item. - - Args: - project_id: UUID of the project - work_item_id: UUID of the work item - params: Optional query parameters as a dictionary - - Returns: - List of WorkItemWorkLog objects - """ - client, workspace_slug = get_plane_client_context() - return client.work_items.work_logs.list( - workspace_slug=workspace_slug, - project_id=project_id, - work_item_id=work_item_id, - params=params, - ) - @mcp.tool() def create_work_log( project_id: str, diff --git a/plane_mcp/tools/workspaces.py b/plane_mcp/tools/workspaces.py index a24e0b97..7398910c 100644 --- a/plane_mcp/tools/workspaces.py +++ b/plane_mcp/tools/workspaces.py @@ -1,7 +1,6 @@ """Workspace-related tools for Plane MCP Server.""" from fastmcp import FastMCP -from plane.models.users import UserLite from plane.models.workspaces import WorkspaceFeature from plane_mcp.client import get_plane_client_context @@ -10,17 +9,6 @@ def register_workspace_tools(mcp: FastMCP) -> None: """Register all workspace-related tools with the MCP server.""" - @mcp.tool() - def get_workspace_members() -> list[UserLite]: - """ - Get all members of the current workspace. - - Returns: - List of UserLite objects representing workspace members - """ - client, workspace_slug = get_plane_client_context() - return client.workspaces.get_members(workspace_slug=workspace_slug) - @mcp.tool() def get_workspace_features() -> WorkspaceFeature: """