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
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action to perform | |
| taskId | No | Task ID (required for get/update/delete/status/assign) | |
| title | No | Task title (for create/update) | |
| description | No | Task description (for create/update) | |
| status | No | Task status class or workflow name (for create/update/change_status) | |
| workflowId | No | Workflow ID (for change_status when you know the exact workflow) | |
| assigneeId | No | Assignee member ID (for assign action) | |
| priority | No | Task priority (for create/update) |
Implementation Reference
- src/dooray_mcp/tools/tasks.py:10-253 (handler)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
- src/dooray_mcp/server.py:44-86 (schema)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"] } ),
- src/dooray_mcp/server.py:40-357 (registration)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)