Skip to main content
Glama
tallpizza

Dooray MCP Server

by tallpizza

dooray_tasks

Manage Dooray project tasks by listing, creating, updating, deleting, changing status, and assigning members through the Dooray MCP Server.

Instructions

Manage Dooray tasks - list, get details, create, update, delete, change status, assign members

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction to perform
taskIdNoTask ID (required for get/update/delete/status/assign)
titleNoTask title (for create/update)
descriptionNoTask description (for create/update)
statusNoTask status class or workflow name (for create/update/change_status)
workflowIdNoWorkflow ID (for change_status when you know the exact workflow)
assigneeIdNoAssignee member ID (for assign action)
priorityNoTask priority (for create/update)

Implementation Reference

  • TasksTool class implements the handler logic for dooray_tasks tool. The async handle method processes the input arguments based on the 'action' field and delegates to specific private methods that interact with DoorayClient.
    class TasksTool:
        """Tool for managing Dooray tasks."""
        
        def __init__(self, dooray_client):
            """Initialize with Dooray client."""
            self.client = dooray_client
        
        async def handle(self, arguments: Dict[str, Any]) -> str:
            """Handle tasks tool requests.
            
            Args:
                arguments: Tool arguments containing action and parameters
                
            Returns:
                JSON string with results
            """
            action = arguments.get("action")
            if not action:
                return json.dumps({"error": "Action parameter is required"})
            
            try:
                if action == "list":
                    return await self._list_tasks(arguments)
                elif action == "get":
                    return await self._get_task(arguments)
                elif action == "create":
                    return await self._create_task(arguments)
                elif action == "update":
                    return await self._update_task(arguments)
                elif action == "delete":
                    return await self._delete_task(arguments)
                elif action == "change_status":
                    return await self._change_status(arguments)
                elif action == "assign":
                    return await self._assign_task(arguments)
                else:
                    return json.dumps({"error": f"Unknown action: {action}"})
                    
            except Exception as e:
                logger.error(f"Error in tasks tool: {e}")
                return json.dumps({"error": str(e)})
        
        def _resolve_project_id(self, arguments: Dict[str, Any]) -> Optional[str]:
            """Resolve project ID from arguments or defaults."""
            project_id = arguments.get("projectId") or self.client.project_id or os.getenv("DOORAY_DEFAULT_PROJECT_ID")
            if project_id:
                return str(project_id)
            return None
    
        def _ensure_task_id(self, arguments: Dict[str, Any]) -> Optional[str]:
            task_id = arguments.get("taskId")
            if task_id is None:
                return None
            return str(task_id)
    
        def _ensure_assignee_id(self, arguments: Dict[str, Any]) -> Optional[str]:
            assignee_id = arguments.get("assigneeId")
            if assignee_id is None:
                return None
            return str(assignee_id)
    
        async def _list_tasks(self, arguments: Dict[str, Any]) -> str:
            """List tasks in a project."""
            project_id = self._resolve_project_id(arguments)
            if not project_id:
                return json.dumps({"error": "projectId is required for list action (set DOORAY_DEFAULT_PROJECT_ID or provide projectId)"})
            
            # Build query parameters
            params = {}
            if arguments.get("status"):
                params["workflowClass"] = arguments["status"]
            assignee_id = self._ensure_assignee_id(arguments)
            if assignee_id:
                params["assigneeId"] = assignee_id
            
            result = await self.client.list_tasks(project_id, **params)
            return json.dumps(result, ensure_ascii=False)
        
        async def _get_task(self, arguments: Dict[str, Any]) -> str:
            """Get a specific task."""
            project_id = self._resolve_project_id(arguments)
            task_id = self._ensure_task_id(arguments)
    
            if not project_id or not task_id:
                return json.dumps({"error": "projectId and taskId are required for get action"})
    
            result = await self.client.get_task(project_id, task_id)
            return json.dumps(result, ensure_ascii=False)
        
        async def _create_task(self, arguments: Dict[str, Any]) -> str:
            """Create a new task."""
            project_id = self._resolve_project_id(arguments)
            title = arguments.get("title")
    
            if not project_id or not title:
                return json.dumps({"error": "projectId and title are required for create action"})
            
            # Build task data
            task_data = {
                "subject": title,
                "body": {
                    "mimeType": "text/x-markdown",
                    "content": arguments.get("description", "")
                }
            }
            
            assignee_id = self._ensure_assignee_id(arguments)
            if assignee_id:
                task_data["users"] = [{"member": {"id": assignee_id}}]
            
            if arguments.get("priority"):
                task_data["priority"] = arguments["priority"]
            
            if arguments.get("status"):
                task_data["workflowClass"] = arguments["status"]
            
            result = await self.client.create_task(project_id, task_data)
            return json.dumps(result, ensure_ascii=False)
        
        async def _update_task(self, arguments: Dict[str, Any]) -> str:
            """Update an existing task."""
            project_id = self._resolve_project_id(arguments)
            task_id = self._ensure_task_id(arguments)
    
            if not project_id or not task_id:
                return json.dumps({"error": "projectId and taskId are required for update action"})
            
            # Build update data
            task_data = {}
            
            if arguments.get("title"):
                task_data["subject"] = arguments["title"]
            
            if arguments.get("description"):
                task_data["body"] = {
                    "mimeType": "text/x-markdown",
                    "content": arguments["description"]
                }
            
            assignee_id = self._ensure_assignee_id(arguments)
            if assignee_id:
                task_data["users"] = [{"member": {"id": assignee_id}}]
    
            if arguments.get("priority"):
                task_data["priority"] = arguments["priority"]
    
            if arguments.get("status"):
                task_data["workflowClass"] = arguments["status"]
            
            result = await self.client.update_task(project_id, task_id, task_data)
            return json.dumps(result, ensure_ascii=False)
        
        async def _delete_task(self, arguments: Dict[str, Any]) -> str:
            """Delete a task."""
            project_id = self._resolve_project_id(arguments)
            task_id = self._ensure_task_id(arguments)
    
            if not project_id or not task_id:
                return json.dumps({"error": "projectId and taskId are required for delete action"})
    
            result = await self.client.delete_task(project_id, task_id)
            return json.dumps({"success": True, "message": "Task deleted successfully"})
        
        async def _change_status(self, arguments: Dict[str, Any]) -> str:
            """Change task status."""
            project_id = self._resolve_project_id(arguments)
            task_id = self._ensure_task_id(arguments)
            status = arguments.get("status")
    
            workflow_id_arg = arguments.get("workflowId")
    
            if not project_id or not task_id or (status is None and workflow_id_arg is None):
                return json.dumps({"error": "projectId, taskId, and either status or workflowId are required for change_status action"})
    
            workflow_id = str(workflow_id_arg) if workflow_id_arg is not None else None
    
            if not workflow_id and status is not None:
                workflow_id = await self._resolve_workflow_id(project_id, status)
    
            if not workflow_id:
                return json.dumps({"error": "Unable to resolve workflow. Provide workflowId or use status/workflow name."}, ensure_ascii=False)
    
            result = await self.client.set_task_workflow(project_id, task_id, workflow_id)
            return json.dumps(result, ensure_ascii=False)
        
        async def _assign_task(self, arguments: Dict[str, Any]) -> str:
            """Assign task to a member."""
            project_id = self._resolve_project_id(arguments)
            task_id = self._ensure_task_id(arguments)
            assignee_id = self._ensure_assignee_id(arguments)
    
            if not task_id or not assignee_id:
                return json.dumps({"error": "taskId and assigneeId are required for assign action"})
            
            task_data = {"users": [{"member": {"id": assignee_id}}]}
            result = await self.client.update_task(project_id, task_id, task_data)
            return json.dumps(result, ensure_ascii=False)
    
        async def _resolve_workflow_id(self, project_id: str, identifier: str) -> Optional[str]:
            if identifier is None:
                return None
    
            identifier_text = str(identifier).strip()
            if not identifier_text:
                return None
    
            workflows = await self.client.list_workflows(project_id)
            items = workflows.get("result") or []
    
            normalized = identifier_text.lower()
            class_matches: list[tuple[int, str]] = []
    
            for item in items:
                workflow_id = str(item.get("id")) if item.get("id") is not None else None
                if not workflow_id:
                    continue
    
                if workflow_id == identifier_text:
                    return workflow_id
    
                name = item.get("name")
                if isinstance(name, str) and name.lower() == normalized:
                    return workflow_id
    
                for localized in item.get("names", []) or []:
                    localized_name = localized.get("name")
                    if isinstance(localized_name, str) and localized_name.lower() == normalized:
                        return workflow_id
    
                workflow_class = item.get("class")
                if isinstance(workflow_class, str) and workflow_class.lower() == normalized:
                    order_value = item.get("order")
                    try:
                        order = int(order_value)
                    except (TypeError, ValueError):
                        order = 0
                    class_matches.append((order, workflow_id))
    
            if class_matches:
                class_matches.sort(key=lambda pair: pair[0])
                return class_matches[0][1]
    
            return None
  • Defines the input schema for the dooray_tasks tool, specifying parameters like action, taskId, title, description, status, etc.
    types.Tool(
        name="dooray_tasks",
        description="Manage Dooray tasks - list, get details, create, update, delete, change status, assign members",
        inputSchema={
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["list", "get", "create", "update", "delete", "change_status", "assign"],
                    "description": "Action to perform"
                },
                "taskId": {
                    "type": "string", 
                    "description": "Task ID (required for get/update/delete/status/assign)"
                },
                "title": {
                    "type": "string",
                    "description": "Task title (for create/update)"
                },
                "description": {
                    "type": "string",
                    "description": "Task description (for create/update)"
                },
                "status": {
                    "type": "string",
                    "description": "Task status class or workflow name (for create/update/change_status)"
                },
                "workflowId": {
                    "type": "string",
                    "description": "Workflow ID (for change_status when you know the exact workflow)"
                },
                "assigneeId": {
                    "type": "string",
                    "description": "Assignee member ID (for assign action)"
                },
                "priority": {
                    "type": "string",
                    "description": "Task priority (for create/update)"
                }
            },
            "required": ["action"]
        }
    ),
  • Registers the dooray_tasks tool via @app.list_tools() returning Tool objects, and handles calls via @app.call_tool() by instantiating TasksTool and calling its handle method.
    @app.list_tools()
    async def handle_list_tools() -> list[types.Tool]:
        """List all available Dooray tools."""
        return [
            types.Tool(
                name="dooray_tasks",
                description="Manage Dooray tasks - list, get details, create, update, delete, change status, assign members",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "action": {
                            "type": "string",
                            "enum": ["list", "get", "create", "update", "delete", "change_status", "assign"],
                            "description": "Action to perform"
                        },
                        "taskId": {
                            "type": "string", 
                            "description": "Task ID (required for get/update/delete/status/assign)"
                        },
                        "title": {
                            "type": "string",
                            "description": "Task title (for create/update)"
                        },
                        "description": {
                            "type": "string",
                            "description": "Task description (for create/update)"
                        },
                        "status": {
                            "type": "string",
                            "description": "Task status class or workflow name (for create/update/change_status)"
                        },
                        "workflowId": {
                            "type": "string",
                            "description": "Workflow ID (for change_status when you know the exact workflow)"
                        },
                        "assigneeId": {
                            "type": "string",
                            "description": "Assignee member ID (for assign action)"
                        },
                        "priority": {
                            "type": "string",
                            "description": "Task priority (for create/update)"
                        }
                    },
                    "required": ["action"]
                }
            ),
            types.Tool(
                name="dooray_comments",
                description="Manage Dooray task comments - get list, create, update, delete comments with mention support",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "action": {
                            "type": "string",
                            "enum": ["list", "create", "update", "delete"],
                            "description": "Action to perform on comments"
                        },
                        "taskId": {
                            "type": "string",
                            "description": "Task ID (required)"
                        },
                        "commentId": {
                            "type": "string",
                            "description": "Comment ID (required for update/delete)"
                        },
                        "content": {
                            "type": "string",
                            "description": "Comment content (for create/update)"
                        },
                        "mentions": {
                            "type": "array",
                            "items": {"type": "string"},
                            "description": "User IDs to mention (optional)"
                        }
                    },
                    "required": ["action", "taskId"]
                }
            ),
            types.Tool(
                name="dooray_tags",
                description="Manage Dooray tags - list available tags, create new tags, add/remove tags from tasks",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "action": {
                            "type": "string",
                            "enum": ["list", "create", "add_to_task", "remove_from_task"],
                            "description": "Action to perform on tags"
                        },
                        "taskId": {
                            "type": "string",
                            "description": "Task ID (required for add_to_task/remove_from_task)"
                        },
                        "tagName": {
                            "type": "string",
                            "description": "Tag name (for create/add_to_task/remove_from_task)"
                        },
                        "tagColor": {
                            "type": "string",
                            "description": "Tag color (for create action, optional)"
                        },
                        "filter": {
                            "type": "string",
                            "description": "Optional substring to filter tag list by name (list action)"
                        }
                    },
                    "required": ["action"]
                }
            ),
            types.Tool(
                name="dooray_search",
                description="Search Dooray content - tasks by various criteria including workflow, assignee, tags, status, date range with AND/OR logic",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "searchType": {
                            "type": "string",
                            "enum": ["tasks", "by_assignee", "by_status", "by_tag", "by_date_range", "by_workflow", "advanced"],
                            "description": "Type of search to perform"
                        },
                        "query": {
                            "type": "string",
                            "description": "Search query text (for tasks search)"
                        },
                        "assigneeId": {
                            "type": "string",
                            "description": "Assignee ID (for by_assignee search)"
                        },
                        "status": {
                            "type": "string",
                            "description": "Task status (for by_status search)"
                        },
                        "tagName": {
                            "type": "string",
                            "description": "Tag name (for by_tag search)"
                        },
                        "workflowId": {
                            "type": "string",
                            "description": "Workflow ID (for by_workflow search)"
                        },
                        "startDate": {
                            "type": "string",
                            "description": "Start date (for by_date_range search)"
                        },
                        "endDate": {
                            "type": "string",
                            "description": "End date (for by_date_range search)"
                        },
                        "conditions": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "type": {
                                        "type": "string",
                                        "enum": ["workflow", "assignee", "tag", "status", "query", "date_range"],
                                        "description": "Type of search condition"
                                    },
                                    "value": {
                                        "type": "string",
                                        "description": "Value for the condition (workflow ID, assignee ID, tag name, status, or query text)"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "description": "Start date (for date_range type)"
                                    },
                                    "endDate": {
                                        "type": "string",
                                        "description": "End date (for date_range type)"
                                    }
                                },
                                "required": ["type"]
                            },
                            "description": "Array of search conditions (for advanced search)"
                        },
                        "logicOperator": {
                            "type": "string",
                            "enum": ["AND", "OR"],
                            "description": "Logic operator for combining conditions in advanced search (default: AND)"
                        },
                        "limit": {
                            "type": "integer",
                            "description": "Maximum results to return (optional)"
                        }
                    },
                    "required": ["searchType"]
                }
            ),
            types.Tool(
                name="dooray_members",
                description="Manage Dooray members - search by email/ID, get member details, check project membership",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "action": {
                            "type": "string",
                            "enum": ["search_by_email", "search_by_id", "get_details", "list_project_members"],
                            "description": "Action to perform on members"
                        },
                        "email": {
                            "type": "string",
                            "description": "Email address (for search_by_email)"
                        },
                        "userId": {
                            "type": "string",
                            "description": "User ID (for search_by_id/get_details)"
                        },
                        "projectId": {
                            "type": "string",
                            "description": "Project ID (optional - uses default from environment if not provided)"
                        }
                    },
                    "required": ["action"]
                }
            ),
            types.Tool(
                name="dooray_files",
                description="Manage Dooray files and images - list task files, get file metadata, download file content from tasks or directly by content ID",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "action": {
                            "type": "string",
                            "enum": ["list_task_files", "get_task_file_metadata", "get_task_file_content", "get_drive_file_metadata", "get_drive_file_content"],
                            "description": "Action to perform on files"
                        },
                        "taskId": {
                            "type": "string",
                            "description": "Task ID (required for task file actions)"
                        },
                        "fileId": {
                            "type": "string",
                            "description": "File ID (required for file operations)"
                        },
                        "projectId": {
                            "type": "string",
                            "description": "Project ID (optional - uses default from environment if not provided, required for task file actions)"
                        }
                    },
                    "required": ["action"]
                }
            ),
            types.Tool(
                name="dooray_workflows",
                description="Manage Dooray workflows - list project workflows, get workflow details, create, update, delete workflows",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "action": {
                            "type": "string",
                            "enum": ["list", "get", "create", "update", "delete"],
                            "description": "Action to perform on workflows"
                        },
                        "workflowId": {
                            "type": "string",
                            "description": "Workflow ID (required for get/update/delete)"
                        },
                        "name": {
                            "type": "string",
                            "description": "Workflow name (for create/update)"
                        },
                        "projectId": {
                            "type": "string",
                            "description": "Project ID (optional - uses default from environment if not provided)"
                        }
                    },
                    "required": ["action"]
                }
            )
        ]
    
    @app.call_tool()
    async def handle_call_tool(name: str, arguments: dict[str, Any] | None) -> list[types.TextContent]:
        """Handle tool calls from Claude."""
        global dooray_client, default_project_id
        
        if not dooray_client:
            return [types.TextContent(
                type="text",
                text="Error: Dooray client not initialized. Please check your DOORAY_API_TOKEN environment variable."
            )]
        
        # Use default project ID from environment for tools that need it
        args = arguments or {}
        
        # Only add projectId for tools that need it (all except some member operations)
        if name in ["dooray_tasks", "dooray_comments", "dooray_tags", "dooray_search", "dooray_workflows"]:
            if not default_project_id:
                return [types.TextContent(
                    type="text",
                    text="Error: DOORAY_DEFAULT_PROJECT_ID environment variable is required"
                )]
            args["projectId"] = default_project_id
        elif name == "dooray_files":
            # Add projectId for task file actions only if not provided
            if args.get("action") in ["list_task_files", "get_task_file_metadata", "get_task_file_content"]:
                if not args.get("projectId") and not default_project_id:
                    return [types.TextContent(
                        type="text",
                        text="Error: DOORAY_DEFAULT_PROJECT_ID environment variable is required for task file operations"
                    )]
                if not args.get("projectId"):
                    args["projectId"] = default_project_id
        elif name == "dooray_members":
            # Only add projectId for list_project_members action
            if args.get("action") == "list_project_members":
                if not default_project_id:
                    return [types.TextContent(
                        type="text",
                        text="Error: DOORAY_DEFAULT_PROJECT_ID environment variable is required"
                    )]
                args["projectId"] = default_project_id
        
        try:
            if name == "dooray_tasks":
                tool = TasksTool(dooray_client)
                result = await tool.handle(args)
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. While it mentions actions like create, update, delete which imply mutations, it doesn't specify authentication requirements, rate limits, side effects, or what happens on deletion. For a multi-action tool with write operations, this is a significant gap in transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise - a single sentence listing all actions. It's front-loaded with the core purpose and wastes no words. Every element earns its place in this compact format.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex multi-action tool with 8 parameters, no annotations, and no output schema, the description is inadequate. It doesn't explain return values, error conditions, or how different actions relate to each other. The agent would struggle to use this tool correctly without significant trial and error.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 8 parameters thoroughly. The description doesn't add any meaningful parameter semantics beyond what's in the schema - it just lists action types without explaining parameter dependencies or usage patterns. Baseline 3 is appropriate when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose as managing Dooray tasks with specific verbs (list, get, create, update, delete, change status, assign members). It distinguishes from siblings by focusing on tasks rather than comments, files, members, etc. However, it doesn't explicitly differentiate from dooray_search which might also involve tasks.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like dooray_search for task-related queries or dooray_workflows for status management. It simply lists capabilities without context about appropriate use cases or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tallpizza/dooray-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server