ClickUp Operator
by noahvanhart
- src
- clickup_operator
import asyncio
from typing import Optional, Any, Sequence
import mcp.types as types
from mcp.server.models import InitializationOptions
from mcp.types import ServerCapabilities
from mcp.server import Server
import mcp.server.stdio
from . import clickup
import os
from dotenv import load_dotenv
import httpx
# Initialize server
app = Server("clickup-operator")
clickup_client: Optional[clickup.ClickUpAPI] = None
# Initialize client first
async def initialize():
"""Initialize the ClickUp client with API token from environment."""
global clickup_client
load_dotenv()
api_key = os.getenv("CLICKUP_API_TOKEN")
if not api_key:
raise ValueError("CLICKUP_API_TOKEN environment variable not set")
max_retries = 3
retry_count = 0
while retry_count < max_retries:
try:
clickup_client = clickup.ClickUpAPI(api_key)
# Test the connection
await clickup_client.get_authorized_user()
return
except Exception as e:
retry_count += 1
if retry_count == max_retries:
raise RuntimeError(f"Failed to initialize ClickUp client after {max_retries} attempts: {str(e)}")
await asyncio.sleep(1) # Wait before retrying
@app.list_tools()
async def list_tools():
"""List available tools for ClickUp operations."""
return [
types.Tool(
name="get-teams",
description="Get all accessible teams/workspaces",
inputSchema={
"type": "object",
"properties": {}
}
),
types.Tool(
name="get-authorized-user",
description="Get information about the currently authorized user",
inputSchema={
"type": "object",
"properties": {}
}
),
types.Tool(
name="create-space",
description="Create a new space in a team",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"name": {"type": "string"},
"multiple_assignees": {"type": "boolean", "optional": True},
"features": {"type": "object", "optional": True}
},
"required": ["team_id", "name"]
}
),
types.Tool(
name="create-folder",
description="Create a new folder in a space",
inputSchema={
"type": "object",
"properties": {
"space_id": {"type": "string"},
"name": {"type": "string"}
},
"required": ["space_id", "name"]
}
),
types.Tool(
name="create-folderless-list",
description="Create a list directly in a space without a folder",
inputSchema={
"type": "object",
"properties": {
"space_id": {"type": "string"},
"name": {"type": "string"},
"content": {"type": "string", "optional": True},
"due_date": {"type": "integer", "optional": True},
"priority": {"type": "integer", "optional": True},
"assignee": {"type": "integer", "optional": True},
"status": {"type": "string", "optional": True}
},
"required": ["space_id", "name"]
}
),
types.Tool(
name="create-task",
description="Create a new task in a list",
inputSchema={
"type": "object",
"properties": {
"list_id": {"type": "string"},
"name": {"type": "string"},
"description": {"type": "string", "optional": True},
"assignees": {"type": "array", "items": {"type": "integer"}, "optional": True},
"tags": {"type": "array", "items": {"type": "string"}, "optional": True},
"status": {"type": "string", "optional": True},
"priority": {"type": "integer", "optional": True},
"due_date": {"type": "integer", "optional": True},
"time_estimate": {"type": "integer", "optional": True},
"notify_all": {"type": "boolean", "optional": True}
},
"required": ["list_id", "name"]
}
),
types.Tool(
name="create-task-comment",
description="Create a comment on a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"},
"comment_text": {"type": "string"},
"assignee": {"type": "integer", "optional": True},
"notify_all": {"type": "boolean", "optional": True}
},
"required": ["task_id", "comment_text"]
}
),
types.Tool(
name="create-checklist",
description="Create a checklist in a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"},
"name": {"type": "string"}
},
"required": ["task_id", "name"]
}
),
types.Tool(
name="get-custom-fields",
description="Get accessible custom fields in a list",
inputSchema={
"type": "object",
"properties": {
"list_id": {"type": "string"}
},
"required": ["list_id"]
}
),
types.Tool(
name="start-time-entry",
description="Start time tracking for a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"},
"description": {"type": "string", "optional": True},
"billable": {"type": "boolean", "optional": True}
},
"required": ["task_id"]
}
),
types.Tool(
name="create-goal",
description="Create a new goal in a team",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"name": {"type": "string"},
"due_date": {"type": "integer", "optional": True},
"description": {"type": "string", "optional": True},
"multiple_owners": {"type": "boolean", "optional": True},
"owners": {"type": "array", "items": {"type": "integer"}, "optional": True},
"color": {"type": "string", "optional": True}
},
"required": ["team_id", "name"]
}
),
types.Tool(
name="create-space-tag",
description="Create a new tag in a space",
inputSchema={
"type": "object",
"properties": {
"space_id": {"type": "string"},
"name": {"type": "string"},
"tag_fg": {"type": "string", "optional": True},
"tag_bg": {"type": "string", "optional": True}
},
"required": ["space_id", "name"]
}
),
types.Tool(
name="add-task-dependency",
description="Add a dependency between tasks",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"},
"depends_on": {"type": "string"},
"dependency_type": {"type": "string", "optional": True}
},
"required": ["task_id", "depends_on"]
}
),
types.Tool(
name="get-task-members",
description="Get members assigned to a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"}
},
"required": ["task_id"]
}
),
types.Tool(
name="invite-guest",
description="Invite a guest to a workspace",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"email": {"type": "string"},
"can_edit_tags": {"type": "boolean", "optional": True},
"can_see_time_estimated": {"type": "boolean", "optional": True},
"can_see_time_spent": {"type": "boolean", "optional": True}
},
"required": ["team_id", "email"]
}
),
types.Tool(
name="get-spaces",
description="Get all spaces in a team",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"}
},
"required": ["team_id"]
}
),
types.Tool(
name="get-lists",
description="Get all lists in a space",
inputSchema={
"type": "object",
"properties": {
"space_id": {"type": "string"}
},
"required": ["space_id"]
}
),
types.Tool(
name="get-tasks",
description="Get tasks from a list",
inputSchema={
"type": "object",
"properties": {
"list_id": {"type": "string"},
"archived": {"type": "boolean", "optional": True},
"page": {"type": "integer", "optional": True},
"order_by": {"type": "string", "optional": True},
"reverse": {"type": "boolean", "optional": True},
"subtasks": {"type": "boolean", "optional": True},
"statuses": {"type": "array", "items": {"type": "string"}, "optional": True},
"include_closed": {"type": "boolean", "optional": True},
"assignees": {"type": "array", "items": {"type": "string"}, "optional": True},
"due_date_gt": {"type": "integer", "optional": True},
"due_date_lt": {"type": "integer", "optional": True},
"date_created_gt": {"type": "integer", "optional": True},
"date_created_lt": {"type": "integer", "optional": True},
"date_updated_gt": {"type": "integer", "optional": True},
"date_updated_lt": {"type": "integer", "optional": True}
},
"required": ["list_id"]
}
),
types.Tool(
name="update-task",
description="Update a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"},
"name": {"type": "string", "optional": True},
"description": {"type": "string", "optional": True},
"status": {"type": "string", "optional": True},
"priority": {"type": "integer", "optional": True},
"due_date": {"type": "integer", "optional": True},
"time_estimate": {"type": "integer", "optional": True},
"assignees": {"type": "array", "items": {"type": "integer"}, "optional": True},
"archived": {"type": "boolean", "optional": True}
},
"required": ["task_id"]
}
),
types.Tool(
name="get-task-watchers",
description="Get watchers of a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"}
},
"required": ["task_id"]
}
),
types.Tool(
name="add-task-watcher",
description="Add a watcher to a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"},
"watcher_id": {"type": "string"}
},
"required": ["task_id", "watcher_id"]
}
),
types.Tool(
name="get-task-dependencies",
description="Get dependencies of a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"}
},
"required": ["task_id"]
}
),
types.Tool(
name="remove-task-dependency",
description="Remove a dependency from a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"},
"dependency_id": {"type": "string"}
},
"required": ["task_id", "dependency_id"]
}
),
types.Tool(
name="get-comments",
description="Get comments on a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"}
},
"required": ["task_id"]
}
),
types.Tool(
name="update-comment",
description="Update a comment",
inputSchema={
"type": "object",
"properties": {
"comment_id": {"type": "string"},
"comment_text": {"type": "string"},
"assignee": {"type": "integer", "optional": True},
"resolved": {"type": "boolean", "optional": True}
},
"required": ["comment_id", "comment_text"]
}
),
types.Tool(
name="delete-comment",
description="Delete a comment",
inputSchema={
"type": "object",
"properties": {
"comment_id": {"type": "string"}
},
"required": ["comment_id"]
}
),
types.Tool(
name="edit-checklist",
description="Edit a checklist",
inputSchema={
"type": "object",
"properties": {
"checklist_id": {"type": "string"},
"name": {"type": "string"}
},
"required": ["checklist_id", "name"]
}
),
types.Tool(
name="delete-checklist",
description="Delete a checklist",
inputSchema={
"type": "object",
"properties": {
"checklist_id": {"type": "string"}
},
"required": ["checklist_id"]
}
),
types.Tool(
name="create-checklist-item",
description="Create an item in a checklist",
inputSchema={
"type": "object",
"properties": {
"checklist_id": {"type": "string"},
"name": {"type": "string"},
"assignee": {"type": "integer", "optional": True},
"due_date": {"type": "integer", "optional": True}
},
"required": ["checklist_id", "name"]
}
),
types.Tool(
name="stop-time-entry",
description="Stop time tracking for a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"}
},
"required": ["task_id"]
}
),
types.Tool(
name="get-time-entries",
description="Get time entries within a date range",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"start_date": {"type": "integer", "optional": True},
"end_date": {"type": "integer", "optional": True},
"assignee": {"type": "integer", "optional": True},
"include_task_tags": {"type": "boolean", "optional": True},
"include_location_names": {"type": "boolean", "optional": True},
"space_id": {"type": "string", "optional": True},
"folder_id": {"type": "string", "optional": True},
"list_id": {"type": "string", "optional": True},
"task_id": {"type": "string", "optional": True},
"custom_task_ids": {"type": "boolean", "optional": True}
},
"required": ["team_id"]
}
),
types.Tool(
name="get-time-entry-history",
description="Get time entry history",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"timer_id": {"type": "string"}
},
"required": ["team_id", "timer_id"]
}
),
types.Tool(
name="get-single-time-entry",
description="Get a single time entry",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"time_entry_id": {"type": "string"}
},
"required": ["team_id", "time_entry_id"]
}
),
types.Tool(
name="get-running-time-entry",
description="Get currently running time entry",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"}
},
"required": ["team_id"]
}
),
types.Tool(
name="update-time-entry",
description="Update a time entry",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"timer_id": {"type": "string"},
"description": {"type": "string", "optional": True},
"start": {"type": "integer", "optional": True},
"duration": {"type": "integer", "optional": True},
"billable": {"type": "boolean", "optional": True},
"tags": {"type": "array", "items": {"type": "string"}, "optional": True}
},
"required": ["team_id", "timer_id"]
}
),
types.Tool(
name="delete-time-entry",
description="Delete a time entry",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"time_entry_id": {"type": "string"}
},
"required": ["team_id", "time_entry_id"]
}
),
types.Tool(
name="get-time-entry-tags",
description="Get all tags from time entries",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"}
},
"required": ["team_id"]
}
),
types.Tool(
name="add-tags-to-time-entries",
description="Add tags to time entries",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"time_entry_ids": {"type": "array", "items": {"type": "string"}},
"tags": {"type": "array", "items": {"type": "object", "properties": {
"name": {"type": "string"},
"tag_bg": {"type": "string", "optional": True},
"tag_fg": {"type": "string", "optional": True}
}}}
},
"required": ["team_id", "time_entry_ids", "tags"]
}
),
types.Tool(
name="remove-tags-from-time-entries",
description="Remove tags from time entries",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"time_entry_ids": {"type": "array", "items": {"type": "string"}},
"tags": {"type": "array", "items": {"type": "object", "properties": {
"name": {"type": "string"}
}}}
},
"required": ["team_id", "time_entry_ids", "tags"]
}
),
types.Tool(
name="update-time-entry-tag",
description="Update a time entry tag",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"name": {"type": "string"},
"new_name": {"type": "string"},
"tag_bg": {"type": "string"},
"tag_fg": {"type": "string"}
},
"required": ["team_id", "name", "new_name", "tag_bg", "tag_fg"]
}
),
types.Tool(
name="edit-guest",
description="Edit a guest's permissions",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"guest_id": {"type": "string"},
"can_edit_tags": {"type": "boolean", "optional": True},
"can_see_time_estimated": {"type": "boolean", "optional": True},
"can_see_time_spent": {"type": "boolean", "optional": True}
},
"required": ["team_id", "guest_id"]
}
),
types.Tool(
name="remove-guest",
description="Remove a guest from workspace",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"guest_id": {"type": "string"}
},
"required": ["team_id", "guest_id"]
}
),
types.Tool(
name="get-guest",
description="Get guest information",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"guest_id": {"type": "string"}
},
"required": ["team_id", "guest_id"]
}
),
types.Tool(
name="add-guest-to-task",
description="Add a guest to a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"},
"guest_id": {"type": "string"},
"permission_level": {"type": "string"}
},
"required": ["task_id", "guest_id", "permission_level"]
}
),
types.Tool(
name="remove-guest-from-task",
description="Remove a guest from a task",
inputSchema={
"type": "object",
"properties": {
"task_id": {"type": "string"},
"guest_id": {"type": "string"}
},
"required": ["task_id", "guest_id"]
}
),
types.Tool(
name="add-guest-to-list",
description="Add a guest to a list",
inputSchema={
"type": "object",
"properties": {
"list_id": {"type": "string"},
"guest_id": {"type": "string"},
"permission_level": {"type": "string"}
},
"required": ["list_id", "guest_id", "permission_level"]
}
),
types.Tool(
name="remove-guest-from-list",
description="Remove a guest from a list",
inputSchema={
"type": "object",
"properties": {
"list_id": {"type": "string"},
"guest_id": {"type": "string"}
},
"required": ["list_id", "guest_id"]
}
),
types.Tool(
name="add-guest-to-folder",
description="Add a guest to a folder",
inputSchema={
"type": "object",
"properties": {
"folder_id": {"type": "string"},
"guest_id": {"type": "string"},
"permission_level": {"type": "string"}
},
"required": ["folder_id", "guest_id", "permission_level"]
}
),
types.Tool(
name="remove-guest-from-folder",
description="Remove a guest from a folder",
inputSchema={
"type": "object",
"properties": {
"folder_id": {"type": "string"},
"guest_id": {"type": "string"}
},
"required": ["folder_id", "guest_id"]
}
),
types.Tool(
name="create-team-view",
description="Create a team view",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"name": {"type": "string"},
"type": {"type": "string"},
"parent": {"type": "string", "optional": True}
},
"required": ["team_id", "name", "type"]
}
),
types.Tool(
name="create-team-group",
description="Create a team (user group)",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"name": {"type": "string"},
"member_ids": {"type": "array", "items": {"type": "integer"}}
},
"required": ["team_id", "name", "member_ids"]
}
),
types.Tool(
name="get-team-groups",
description="Get teams (user groups)",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"}
},
"required": ["team_id"]
}
),
types.Tool(
name="update-team-group",
description="Update a team (user group)",
inputSchema={
"type": "object",
"properties": {
"group_id": {"type": "string"},
"name": {"type": "string", "optional": True},
"member_ids": {"type": "array", "items": {"type": "integer"}, "optional": True}
},
"required": ["group_id"]
}
),
types.Tool(
name="delete-team-group",
description="Delete a team (user group)",
inputSchema={
"type": "object",
"properties": {
"group_id": {"type": "string"}
},
"required": ["group_id"]
}
),
types.Tool(
name="get-workspace-seats",
description="Get workspace seats",
inputSchema={
"type": "object",
"properties": {
"workspace_id": {"type": "string"}
},
"required": ["workspace_id"]
}
),
types.Tool(
name="get-task-templates",
description="Get task templates",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"page": {"type": "integer", "optional": True}
},
"required": ["team_id"]
}
),
types.Tool(
name="create-task-from-template",
description="Create a task from a template",
inputSchema={
"type": "object",
"properties": {
"list_id": {"type": "string"},
"template_id": {"type": "string"},
"name": {"type": "string"}
},
"required": ["list_id", "template_id", "name"]
}
),
types.Tool(
name="invite-user",
description="Invite a user to a workspace",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"email": {"type": "string"},
"admin": {"type": "boolean", "optional": True},
"custom_role_id": {"type": "integer", "optional": True}
},
"required": ["team_id", "email"]
}
),
types.Tool(
name="edit-user",
description="Edit a user in a workspace",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"user_id": {"type": "integer"},
"username": {"type": "string", "optional": True},
"email": {"type": "string", "optional": True},
"color": {"type": "string", "optional": True},
"initials": {"type": "string", "optional": True},
"role": {"type": "integer", "optional": True}
},
"required": ["team_id", "user_id"]
}
),
types.Tool(
name="remove-user",
description="Remove a user from a workspace",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"user_id": {"type": "integer"}
},
"required": ["team_id", "user_id"]
}
),
types.Tool(
name="get-user",
description="Get user information",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"user_id": {"type": "integer"}
},
"required": ["team_id", "user_id"]
}
),
types.Tool(
name="create-webhook",
description="Create a webhook",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"},
"endpoint": {"type": "string"},
"events": {"type": "array", "items": {"type": "string"}},
"space_id": {"type": "string", "optional": True},
"list_id": {"type": "string", "optional": True},
"task_id": {"type": "string", "optional": True},
"health_check_url": {"type": "string", "optional": True}
},
"required": ["team_id", "endpoint", "events"]
}
),
types.Tool(
name="get-webhooks",
description="Get webhooks",
inputSchema={
"type": "object",
"properties": {
"team_id": {"type": "string"}
},
"required": ["team_id"]
}
),
types.Tool(
name="update-webhook",
description="Update a webhook",
inputSchema={
"type": "object",
"properties": {
"webhook_id": {"type": "string"},
"endpoint": {"type": "string", "optional": True},
"events": {"type": "array", "items": {"type": "string"}, "optional": True},
"space_id": {"type": "string", "optional": True},
"list_id": {"type": "string", "optional": True},
"task_id": {"type": "string", "optional": True},
"health_check_url": {"type": "string", "optional": True},
"status": {"type": "string", "optional": True}
},
"required": ["webhook_id"]
}
),
types.Tool(
name="delete-webhook",
description="Delete a webhook",
inputSchema={
"type": "object",
"properties": {
"webhook_id": {"type": "string"}
},
"required": ["webhook_id"]
}
)
]
@app.call_tool()
async def handle_call_tool(
name: str,
arguments: dict[str, Any] | None
) -> Sequence[types.TextContent | types.ImageContent]:
"""Handle ClickUp tool execution requests."""
global clickup_client
# Initialize client if not already initialized
if not clickup_client:
await initialize()
if not clickup_client: # Double check initialization worked
raise RuntimeError("Failed to initialize ClickUp client")
# Authorization endpoints
if name == "get-authorized-user":
user = await clickup_client.get_authorized_user()
return [types.TextContent(
type="text",
text=f"Authorized user: {user['username']} (ID: {user['id']})"
)]
# Spaces endpoints
elif name == "create-space":
if not arguments:
raise ValueError("Missing arguments")
space = await clickup_client.create_space(
team_id=arguments["team_id"],
name=arguments["name"],
multiple_assignees=arguments.get("multiple_assignees"),
features=arguments.get("features", {})
)
return [types.TextContent(
type="text",
text=f"Created space: {space['name']} (ID: {space['id']})"
)]
# Folders endpoints
elif name == "create-folder":
if not arguments:
raise ValueError("Missing arguments")
folder = await clickup_client.create_folder(
space_id=arguments["space_id"],
name=arguments["name"]
)
return [types.TextContent(
type="text",
text=f"Created folder: {folder['name']} (ID: {folder['id']})"
)]
# Lists endpoints
elif name == "create-folderless-list":
if not arguments:
raise ValueError("Missing arguments")
lst = await clickup_client.create_folderless_list(
space_id=arguments["space_id"],
name=arguments["name"],
content=arguments.get("content"),
due_date=arguments.get("due_date"),
priority=arguments.get("priority"),
assignee=arguments.get("assignee"),
status=arguments.get("status")
)
return [types.TextContent(
type="text",
text=f"Created list: {lst['name']} (ID: {lst['id']})"
)]
# Tasks endpoints
elif name == "create-task":
if not arguments:
raise ValueError("Missing arguments")
task = await clickup_client.create_task(
list_id=arguments["list_id"],
name=arguments["name"],
description=arguments.get("description"),
assignees=arguments.get("assignees", []),
tags=arguments.get("tags", []),
status=arguments.get("status"),
priority=arguments.get("priority"),
due_date=arguments.get("due_date"),
time_estimate=arguments.get("time_estimate"),
notify_all=arguments.get("notify_all", False)
)
return [types.TextContent(
type="text",
text=f"Created task: {task['name']} (ID: {task['id']})"
)]
# Comments endpoints
elif name == "create-task-comment":
if not arguments:
raise ValueError("Missing arguments")
comment = await clickup_client.create_task_comment(
task_id=arguments["task_id"],
comment_text=arguments["comment_text"],
assignee=arguments.get("assignee"),
notify_all=arguments.get("notify_all", False)
)
return [types.TextContent(
type="text",
text=f"Created comment: {comment['id']}"
)]
# Checklists endpoints
elif name == "create-checklist":
if not arguments:
raise ValueError("Missing arguments")
checklist = await clickup_client.create_checklist(
task_id=arguments["task_id"],
name=arguments["name"]
)
return [types.TextContent(
type="text",
text=f"Created checklist: {checklist['name']} (ID: {checklist['id']})"
)]
# Custom fields endpoints
elif name == "get-custom-fields":
if not arguments:
raise ValueError("Missing arguments")
fields = await clickup_client.get_accessible_custom_fields(arguments["list_id"])
fields_text = "\n".join([
f"- {field['name']} (Type: {field['type']}, ID: {field['id']})"
for field in fields
])
return [types.TextContent(
type="text",
text=f"Custom fields:\n{fields_text}"
)]
# Time tracking endpoints
elif name == "start-time-entry":
if not arguments:
raise ValueError("Missing arguments")
entry = await clickup_client.start_time_entry(
task_id=arguments["task_id"],
description=arguments.get("description"),
billable=arguments.get("billable")
)
return [types.TextContent(
type="text",
text=f"Started time entry: {entry['id']}"
)]
# Goals endpoints
elif name == "create-goal":
if not arguments:
raise ValueError("Missing arguments")
goal = await clickup_client.create_goal(
team_id=arguments["team_id"],
name=arguments["name"],
due_date=arguments.get("due_date"),
description=arguments.get("description"),
multiple_owners=arguments.get("multiple_owners"),
owners=arguments.get("owners", []),
color=arguments.get("color")
)
return [types.TextContent(
type="text",
text=f"Created goal: {goal['name']} (ID: {goal['id']})"
)]
# Tags endpoints
elif name == "create-space-tag":
if not arguments:
raise ValueError("Missing arguments")
tag = await clickup_client.create_space_tag(
space_id=arguments["space_id"],
name=arguments["name"],
tag_fg=arguments.get("tag_fg"),
tag_bg=arguments.get("tag_bg")
)
return [types.TextContent(
type="text",
text=f"Created tag: {tag['name']}"
)]
# Dependencies endpoints
elif name == "add-task-dependency":
if not arguments:
raise ValueError("Missing arguments")
dependency = await clickup_client.add_task_dependency(
task_id=arguments["task_id"],
depends_on=arguments["depends_on"],
dependency_type=arguments.get("dependency_type", "waiting_on")
)
return [types.TextContent(
type="text",
text=f"Added dependency: {dependency['id']}"
)]
# Members endpoints
elif name == "get-task-members":
if not arguments:
raise ValueError("Missing arguments")
members = await clickup_client.get_task_members(arguments["task_id"])
members_text = "\n".join([
f"- {member.get('username', 'Unknown')} (ID: {member.get('id', 'Unknown')})"
for member in members
])
return [types.TextContent(
type="text",
text=f"Task members:\n{members_text}"
)]
# Guests endpoints
elif name == "invite-guest":
if not arguments:
raise ValueError("Missing arguments")
guest = await clickup_client.invite_guest(
team_id=arguments["team_id"],
email=arguments["email"],
can_edit_tags=arguments.get("can_edit_tags"),
can_see_time_estimated=arguments.get("can_see_time_estimated"),
can_see_time_spent=arguments.get("can_see_time_spent")
)
return [types.TextContent(
type="text",
text=f"Invited guest: {guest['email']}"
)]
# Teams endpoints
elif name == "get-teams":
teams = await clickup_client.get_teams()
teams_text = "\n".join([
f"- {team.get('name', 'Unknown')} (ID: {team.get('id', 'Unknown')})"
for team in teams
])
return [types.TextContent(
type="text",
text=f"Available teams:\n{teams_text}"
)]
# Spaces endpoints
elif name == "get-spaces":
if not arguments:
raise ValueError("Missing arguments")
spaces = await clickup_client.get_spaces(arguments["team_id"])
spaces_text = "\n".join([
f"- {space.get('name', 'Unknown')} (ID: {space.get('id', 'Unknown')})"
for space in spaces
])
return [types.TextContent(
type="text",
text=f"Available spaces:\n{spaces_text}"
)]
elif name == "get-lists":
if not arguments:
raise ValueError("Missing arguments")
lists = await clickup_client.get_lists(arguments["space_id"])
lists_text = "\n".join([
f"- {lst.get('name', 'Unknown')} (ID: {lst.get('id', 'Unknown')})"
for lst in lists
])
return [types.TextContent(
type="text",
text=f"Available lists:\n{lists_text}"
)]
# Tasks endpoints
elif name == "get-tasks":
if not arguments:
raise ValueError("Missing arguments")
tasks = await clickup_client.get_tasks(arguments["list_id"], **{k: v for k, v in arguments.items() if k != "list_id"})
tasks_text = "\n".join([
f"- {task.get('name', 'Unknown')} (ID: {task.get('id', 'Unknown')})"
for task in tasks
])
return [types.TextContent(
type="text",
text=f"Tasks:\n{tasks_text}"
)]
elif name == "update-task":
if not arguments:
raise ValueError("Missing arguments")
task_id = arguments.pop("task_id")
task = await clickup_client.update_task(task_id, **arguments)
return [types.TextContent(
type="text",
text=f"Updated task: {task.get('name', 'Unknown')} (ID: {task.get('id', 'Unknown')})"
)]
elif name == "get-task-watchers":
if not arguments:
raise ValueError("Missing arguments")
watchers = await clickup_client.get_task_watchers(arguments["task_id"])
watchers_text = "\n".join([
f"- {watcher.get('username', 'Unknown')} (ID: {watcher.get('id', 'Unknown')})"
for watcher in watchers
])
return [types.TextContent(
type="text",
text=f"Task watchers:\n{watchers_text}"
)]
elif name == "add-task-watcher":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.add_task_watcher(
task_id=arguments["task_id"],
watcher_id=arguments["watcher_id"]
)
return [types.TextContent(
type="text",
text=f"Added watcher to task"
)]
elif name == "get-task-dependencies":
if not arguments:
raise ValueError("Missing arguments")
dependencies = await clickup_client.get_task_dependencies(arguments["task_id"])
deps_text = "\n".join([
f"- {dep.get('task_id', 'Unknown')} ({dep.get('type', 'Unknown')})"
for dep in dependencies
])
return [types.TextContent(
type="text",
text=f"Task dependencies:\n{deps_text}"
)]
elif name == "remove-task-dependency":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.remove_task_dependency(
task_id=arguments["task_id"],
dependency_id=arguments["dependency_id"]
)
return [types.TextContent(
type="text",
text=f"Removed task dependency"
)]
# Comments endpoints
elif name == "get-comments":
if not arguments:
raise ValueError("Missing arguments")
comments = await clickup_client.get_comments(arguments["task_id"])
comments_text = "\n".join([
f"- {comment.get('comment_text', 'No text')} (ID: {comment.get('id', 'Unknown')})"
for comment in comments
])
return [types.TextContent(
type="text",
text=f"Task comments:\n{comments_text}"
)]
elif name == "update-comment":
if not arguments:
raise ValueError("Missing arguments")
comment_id = arguments.pop("comment_id")
comment_text = arguments.pop("comment_text")
result = await clickup_client.update_comment(
comment_id=comment_id,
comment_text=comment_text,
**arguments
)
return [types.TextContent(
type="text",
text=f"Updated comment"
)]
elif name == "delete-comment":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.delete_comment(arguments["comment_id"])
return [types.TextContent(
type="text",
text=f"Deleted comment"
)]
# Checklists endpoints
elif name == "edit-checklist":
if not arguments:
raise ValueError("Missing arguments")
checklist = await clickup_client.edit_checklist(
checklist_id=arguments["checklist_id"],
name=arguments["name"]
)
return [types.TextContent(
type="text",
text=f"Updated checklist: {checklist.get('name', 'Unknown')} (ID: {checklist.get('id', 'Unknown')})"
)]
elif name == "delete-checklist":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.delete_checklist(arguments["checklist_id"])
return [types.TextContent(
type="text",
text=f"Deleted checklist"
)]
elif name == "create-checklist-item":
if not arguments:
raise ValueError("Missing arguments")
checklist_id = arguments.pop("checklist_id")
name = arguments.pop("name")
item = await clickup_client.create_checklist_item(
checklist_id=checklist_id,
name=name,
**arguments
)
return [types.TextContent(
type="text",
text=f"Created checklist item: {item.get('name', 'Unknown')} (ID: {item.get('id', 'Unknown')})"
)]
# Time tracking endpoints
elif name == "stop-time-entry":
if not arguments:
raise ValueError("Missing arguments")
entry = await clickup_client.stop_time_entry(arguments["task_id"])
return [types.TextContent(
type="text",
text=f"Stopped time entry: {entry.get('id', 'Unknown')}"
)]
elif name == "get-time-entries":
if not arguments:
raise ValueError("Missing arguments")
team_id = arguments.pop("team_id")
entries = await clickup_client.get_time_entries(team_id, **arguments)
entries_text = "\n".join([
f"- {entry.get('description', 'No description')} ({entry.get('duration', 0)} seconds)"
for entry in entries
])
return [types.TextContent(
type="text",
text=f"Time entries:\n{entries_text}"
)]
elif name == "get-time-entry-history":
if not arguments:
raise ValueError("Missing arguments")
history = await clickup_client.get_time_entry_history(
team_id=arguments["team_id"],
timer_id=arguments["timer_id"]
)
history_text = "\n".join([
f"- {entry.get('action', 'Unknown')} at {entry.get('date', 'Unknown')}"
for entry in history
])
return [types.TextContent(
type="text",
text=f"Time entry history:\n{history_text}"
)]
elif name == "get-single-time-entry":
if not arguments:
raise ValueError("Missing arguments")
entry = await clickup_client.get_single_time_entry(
team_id=arguments["team_id"],
time_entry_id=arguments["time_entry_id"]
)
return [types.TextContent(
type="text",
text=f"Time entry: {entry.get('description', 'No description')} ({entry.get('duration', 0)} seconds)"
)]
elif name == "get-running-time-entry":
if not arguments:
raise ValueError("Missing arguments")
entry = await clickup_client.get_running_time_entry(arguments["team_id"])
if entry:
return [types.TextContent(
type="text",
text=f"Running time entry: {entry.get('description', 'No description')} ({entry.get('duration', 0)} seconds)"
)]
else:
return [types.TextContent(
type="text",
text="No running time entry found"
)]
elif name == "update-time-entry":
if not arguments:
raise ValueError("Missing arguments")
team_id = arguments.pop("team_id")
timer_id = arguments.pop("timer_id")
entry = await clickup_client.update_time_entry(
team_id=team_id,
timer_id=timer_id,
**arguments
)
return [types.TextContent(
type="text",
text=f"Updated time entry: {entry.get('description', 'No description')} ({entry.get('duration', 0)} seconds)"
)]
elif name == "delete-time-entry":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.delete_time_entry(
team_id=arguments["team_id"],
time_entry_id=arguments["time_entry_id"]
)
return [types.TextContent(
type="text",
text=f"Deleted time entry"
)]
# Time Entry Tags endpoints
elif name == "get-time-entry-tags":
if not arguments:
raise ValueError("Missing arguments")
tags = await clickup_client.get_time_entry_tags(arguments["team_id"])
tags_text = "\n".join([
f"- {tag}"
for tag in tags
])
return [types.TextContent(
type="text",
text=f"Time entry tags:\n{tags_text}"
)]
elif name == "add-tags-to-time-entries":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.add_tags_to_time_entries(
team_id=arguments["team_id"],
time_entry_ids=arguments["time_entry_ids"],
tags=arguments["tags"]
)
return [types.TextContent(
type="text",
text=f"Added tags to time entries"
)]
elif name == "remove-tags-from-time-entries":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.remove_tags_from_time_entries(
team_id=arguments["team_id"],
time_entry_ids=arguments["time_entry_ids"],
tags=arguments["tags"]
)
return [types.TextContent(
type="text",
text=f"Removed tags from time entries"
)]
elif name == "update-time-entry-tag":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.update_time_entry_tag(
team_id=arguments["team_id"],
name=arguments["name"],
new_name=arguments["new_name"],
tag_bg=arguments["tag_bg"],
tag_fg=arguments["tag_fg"]
)
return [types.TextContent(
type="text",
text=f"Updated time entry tag"
)]
# Guest management endpoints
elif name == "edit-guest":
if not arguments:
raise ValueError("Missing arguments")
team_id = arguments.pop("team_id")
guest_id = arguments.pop("guest_id")
guest = await clickup_client.edit_guest(team_id, guest_id, **arguments)
return [types.TextContent(
type="text",
text=f"Updated guest permissions"
)]
elif name == "remove-guest":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.remove_guest(
team_id=arguments["team_id"],
guest_id=arguments["guest_id"]
)
return [types.TextContent(
type="text",
text=f"Removed guest from workspace"
)]
elif name == "get-guest":
if not arguments:
raise ValueError("Missing arguments")
guest = await clickup_client.get_guest(
team_id=arguments["team_id"],
guest_id=arguments["guest_id"]
)
return [types.TextContent(
type="text",
text=f"Guest: {guest.get('email', 'Unknown')} (ID: {guest.get('id', 'Unknown')})"
)]
elif name == "add-guest-to-task":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.add_guest_to_task(
task_id=arguments["task_id"],
guest_id=arguments["guest_id"],
permission_level=arguments["permission_level"]
)
return [types.TextContent(
type="text",
text=f"Added guest to task"
)]
elif name == "remove-guest-from-task":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.remove_guest_from_task(
task_id=arguments["task_id"],
guest_id=arguments["guest_id"]
)
return [types.TextContent(
type="text",
text=f"Removed guest from task"
)]
elif name == "add-guest-to-list":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.add_guest_to_list(
list_id=arguments["list_id"],
guest_id=arguments["guest_id"],
permission_level=arguments["permission_level"]
)
return [types.TextContent(
type="text",
text=f"Added guest to list"
)]
elif name == "remove-guest-from-list":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.remove_guest_from_list(
list_id=arguments["list_id"],
guest_id=arguments["guest_id"]
)
return [types.TextContent(
type="text",
text=f"Removed guest from list"
)]
elif name == "add-guest-to-folder":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.add_guest_to_folder(
folder_id=arguments["folder_id"],
guest_id=arguments["guest_id"],
permission_level=arguments["permission_level"]
)
return [types.TextContent(
type="text",
text=f"Added guest to folder"
)]
elif name == "remove-guest-from-folder":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.remove_guest_from_folder(
folder_id=arguments["folder_id"],
guest_id=arguments["guest_id"]
)
return [types.TextContent(
type="text",
text=f"Removed guest from folder"
)]
# Views endpoints
elif name == "create-team-view":
if not arguments:
raise ValueError("Missing arguments")
team_id = arguments.pop("team_id")
view = await clickup_client.create_team_view(team_id, **arguments)
return [types.TextContent(
type="text",
text=f"Created team view: {view.get('name', 'Unknown')} (ID: {view.get('id', 'Unknown')})"
)]
# Teams endpoints
elif name == "create-team-group":
if not arguments:
raise ValueError("Missing arguments")
group = await clickup_client.create_team_group(
team_id=arguments["team_id"],
name=arguments["name"],
member_ids=arguments["member_ids"]
)
return [types.TextContent(
type="text",
text=f"Created team group: {group.get('name', 'Unknown')} (ID: {group.get('id', 'Unknown')})"
)]
elif name == "get-team-groups":
if not arguments:
raise ValueError("Missing arguments")
groups = await clickup_client.get_team_groups(arguments["team_id"])
groups_text = "\n".join([
f"- {group.get('name', 'Unknown')} (ID: {group.get('id', 'Unknown')})"
for group in groups.get("groups", [])
])
return [types.TextContent(
type="text",
text=f"Team groups:\n{groups_text}"
)]
elif name == "update-team-group":
if not arguments:
raise ValueError("Missing arguments")
group_id = arguments.pop("group_id")
group = await clickup_client.update_team_group(group_id, **arguments)
return [types.TextContent(
type="text",
text=f"Updated team group: {group.get('name', 'Unknown')} (ID: {group.get('id', 'Unknown')})"
)]
elif name == "delete-team-group":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.delete_team_group(arguments["group_id"])
return [types.TextContent(
type="text",
text=f"Deleted team group"
)]
elif name == "get-workspace-seats":
if not arguments:
raise ValueError("Missing arguments")
seats = await clickup_client.get_workspace_seats(arguments["workspace_id"])
return [types.TextContent(
type="text",
text=f"Workspace seats: {seats}"
)]
# Task Templates endpoints
elif name == "get-task-templates":
if not arguments:
raise ValueError("Missing arguments")
templates = await clickup_client.get_task_templates(
team_id=arguments["team_id"],
page=arguments.get("page", 0)
)
templates_text = "\n".join([
f"- {template.get('name', 'Unknown')} (ID: {template.get('id', 'Unknown')})"
for template in templates
])
return [types.TextContent(
type="text",
text=f"Task templates:\n{templates_text}"
)]
elif name == "create-task-from-template":
if not arguments:
raise ValueError("Missing arguments")
task = await clickup_client.create_task_from_template(
list_id=arguments["list_id"],
template_id=arguments["template_id"],
name=arguments["name"]
)
return [types.TextContent(
type="text",
text=f"Created task from template: {task.get('name', 'Unknown')} (ID: {task.get('id', 'Unknown')})"
)]
# User management endpoints
elif name == "invite-user":
if not arguments:
raise ValueError("Missing arguments")
team_id = arguments.pop("team_id")
email = arguments.pop("email")
user = await clickup_client.invite_user(team_id, email, **arguments)
return [types.TextContent(
type="text",
text=f"Invited user: {user.get('email', 'Unknown')}"
)]
elif name == "edit-user":
if not arguments:
raise ValueError("Missing arguments")
team_id = arguments.pop("team_id")
user_id = arguments.pop("user_id")
user = await clickup_client.edit_user(team_id, user_id, **arguments)
return [types.TextContent(
type="text",
text=f"Updated user: {user.get('username', 'Unknown')} (ID: {user.get('id', 'Unknown')})"
)]
elif name == "remove-user":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.remove_user(
team_id=arguments["team_id"],
user_id=arguments["user_id"]
)
return [types.TextContent(
type="text",
text=f"Removed user from workspace"
)]
elif name == "get-user":
if not arguments:
raise ValueError("Missing arguments")
user = await clickup_client.get_user(
team_id=arguments["team_id"],
user_id=arguments["user_id"]
)
return [types.TextContent(
type="text",
text=f"User: {user.get('username', 'Unknown')} (ID: {user.get('id', 'Unknown')})"
)]
# Webhook endpoints
elif name == "create-webhook":
if not arguments:
raise ValueError("Missing arguments")
team_id = arguments.pop("team_id")
endpoint = arguments.pop("endpoint")
events = arguments.pop("events")
webhook = await clickup_client.create_webhook(team_id, endpoint, events, **arguments)
return [types.TextContent(
type="text",
text=f"Created webhook: {webhook.get('id', 'Unknown')}"
)]
elif name == "get-webhooks":
if not arguments:
raise ValueError("Missing arguments")
webhooks = await clickup_client.get_webhooks(arguments["team_id"])
webhooks_text = "\n".join([
f"- {webhook.get('endpoint', 'Unknown')} (ID: {webhook.get('id', 'Unknown')})"
for webhook in webhooks.get("webhooks", [])
])
return [types.TextContent(
type="text",
text=f"Webhooks:\n{webhooks_text}"
)]
elif name == "update-webhook":
if not arguments:
raise ValueError("Missing arguments")
webhook_id = arguments.pop("webhook_id")
webhook = await clickup_client.update_webhook(webhook_id, **arguments)
return [types.TextContent(
type="text",
text=f"Updated webhook: {webhook.get('id', 'Unknown')}"
)]
elif name == "delete-webhook":
if not arguments:
raise ValueError("Missing arguments")
result = await clickup_client.delete_webhook(arguments["webhook_id"])
return [types.TextContent(
type="text",
text=f"Deleted webhook"
)]
else:
raise ValueError(f"Unknown tool: {name}")
# This should never be reached due to the else block above
raise RuntimeError("Unexpected execution path")
async def main():
"""Main entry point for the server."""
async with mcp.server.stdio.stdio_server() as (input_stream, output_stream):
await app.run(
input_stream,
output_stream,
InitializationOptions(
server_name="clickup-operator",
server_version="0.1.0",
capabilities=ServerCapabilities()
)
)
if __name__ == "__main__":
asyncio.run(main())