From 3649d48ec70f069039101d817eacbad7b375ab4b Mon Sep 17 00:00:00 2001 From: Oshri Fatkiev Date: Mon, 18 May 2026 06:49:52 +0300 Subject: [PATCH] fix: return success from module work item add tool --- plane_mcp/tools/modules.py | 14 ++++++-- tests/test_integration.py | 2 +- tests/test_modules.py | 69 ++++++++++++++++++++++++++++++++++++ tests/test_oauth_security.py | 1 - tests/test_stateless_http.py | 1 - 5 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 tests/test_modules.py diff --git a/plane_mcp/tools/modules.py b/plane_mcp/tools/modules.py index 412758e..ea8a30d 100644 --- a/plane_mcp/tools/modules.py +++ b/plane_mcp/tools/modules.py @@ -1,6 +1,6 @@ """Module-related tools for Plane MCP Server.""" -from typing import Any, get_args +from typing import Any, TypedDict, get_args from fastmcp import FastMCP from plane.models.enums import ModuleStatusEnum @@ -17,6 +17,12 @@ from plane_mcp.client import get_plane_client_context +class AddWorkItemsToModuleResult(TypedDict): + """Result returned after successfully adding work items to a module.""" + + success: bool + + def register_module_tools(mcp: FastMCP) -> None: """Register all module-related tools with the MCP server.""" @@ -208,7 +214,7 @@ def add_work_items_to_module( project_id: str, module_id: str, work_item_ids: list[str], - ) -> None: + ) -> AddWorkItemsToModuleResult: """ Add work items to a module. @@ -216,6 +222,9 @@ def add_work_items_to_module( project_id: UUID of the project module_id: UUID of the module work_item_ids: List of work item UUIDs to add to the module + + Returns: + Success status for the operation """ client, workspace_slug = get_plane_client_context() client.modules.add_work_items( @@ -224,6 +233,7 @@ def add_work_items_to_module( module_id=module_id, issue_ids=work_item_ids, ) + return {"success": True} @mcp.tool() def remove_work_item_from_module( diff --git a/tests/test_integration.py b/tests/test_integration.py index 9ff5635..3c4e39e 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -44,7 +44,7 @@ def extract_result(result): if hasattr(content, "text"): try: return json.loads(content.text) - except: + except json.JSONDecodeError: return {"raw": content.text} return {} diff --git a/tests/test_modules.py b/tests/test_modules.py new file mode 100644 index 0000000..4675957 --- /dev/null +++ b/tests/test_modules.py @@ -0,0 +1,69 @@ +"""Tests for module tools.""" + +import asyncio + +from fastmcp import Client, FastMCP + +from plane_mcp.tools import modules as module_tools + + +class FakeModulesClient: + def __init__(self) -> None: + self.add_work_items_calls: list[dict[str, object]] = [] + + def add_work_items( + self, + workspace_slug: str, + project_id: str, + module_id: str, + issue_ids: list[str], + ) -> list[dict[str, str]]: + self.add_work_items_calls.append( + { + "workspace_slug": workspace_slug, + "project_id": project_id, + "module_id": module_id, + "issue_ids": issue_ids, + } + ) + return [{"id": "module-issue-id", "module": module_id, "issue": issue_ids[0]}] + + +class FakePlaneClient: + def __init__(self) -> None: + self.modules = FakeModulesClient() + + +def test_add_work_items_to_module_returns_success_payload(monkeypatch) -> None: + """A successful SDK list response should not leak into MCP response validation.""" + fake_client = FakePlaneClient() + monkeypatch.setattr( + module_tools, + "get_plane_client_context", + lambda: (fake_client, "test-workspace"), + ) + + async def call_tool() -> object: + mcp = FastMCP("test") + module_tools.register_module_tools(mcp) + + async with Client(mcp) as client: + result = await client.call_tool( + "add_work_items_to_module", + { + "project_id": "project-id", + "module_id": "module-id", + "work_item_ids": ["work-item-id"], + }, + ) + return result.structured_content + + assert asyncio.run(call_tool()) == {"success": True} + assert fake_client.modules.add_work_items_calls == [ + { + "workspace_slug": "test-workspace", + "project_id": "project-id", + "module_id": "module-id", + "issue_ids": ["work-item-id"], + } + ] diff --git a/tests/test_oauth_security.py b/tests/test_oauth_security.py index 50f11b8..f0fc670 100644 --- a/tests/test_oauth_security.py +++ b/tests/test_oauth_security.py @@ -22,7 +22,6 @@ from plane_mcp.auth import PlaneOAuthProvider - # Exact allowed patterns from plane_mcp/server.py ALLOWED_REDIRECT_URI_PATTERNS = [ "http://localhost:*", diff --git a/tests/test_stateless_http.py b/tests/test_stateless_http.py index 34a4fbf..9e1639f 100644 --- a/tests/test_stateless_http.py +++ b/tests/test_stateless_http.py @@ -12,7 +12,6 @@ from plane_mcp.auth import PlaneHeaderAuthProvider, PlaneOAuthProvider - ALLOWED_REDIRECT_URI_PATTERNS = [ "http://localhost:*", "http://localhost:*/*",