MCP server for ClickUp API. Manage projects, tasks, time tracking, goals, and team collaboration with a flexible hierarchical project management system.
- Hierarchical Organization: Workspace → Space → Folder → List → Task
- Task Management: Create, update, and track tasks with custom fields
- Time Tracking: Log time entries and estimates
- Goals: Set and track goals with progress metrics
- Custom Fields: Flexible metadata for tasks
- Collaboration: Comments, assignments, and notifications
- Search: Find tasks across your workspace
- Statuses: Customizable per-list status workflows
- ClickUp account (free or paid)
- API token
CLICKUP_API_TOKEN
(required): Your ClickUp API token
How to get credentials:
- Go to app.clickup.com
- Log in to your account
- Click your avatar → Settings
- Go to "Apps" in the sidebar
- Click "Generate" under "API Token"
- Copy the token (starts with
pk_
) - Store as
CLICKUP_API_TOKEN
Direct link: https://app.clickup.com/settings/apps
Important: Keep your API token secure. It has full access to your ClickUp account.
- Standard: 100 requests per minute per token
- Rate limit headers included in responses
- HTTP 429 response when limit exceeded
- Consider caching for frequently accessed data
ClickUp uses a hierarchical structure:
Workspace (Team)
└─ Space
├─ Folder (optional)
│ └─ List
│ └─ Task
│ └─ Subtask
└─ List (folderless)
└─ Task
List all accessible workspaces (teams).
Example:
workspaces = await list_workspaces()
# Returns:
# {
# "teams": [
# {
# "id": "123",
# "name": "My Workspace",
# "color": "#FF0000",
# "avatar": "https://...",
# "members": [...]
# }
# ]
# }
List spaces in a workspace.
Parameters:
workspace_id
(string, required): Workspace IDarchived
(bool, optional): Include archived (default: false)
Example:
spaces = await list_spaces(workspace_id="123")
# Returns:
# {
# "spaces": [
# {
# "id": "456",
# "name": "Marketing",
# "private": false,
# "statuses": [...],
# "multiple_assignees": true,
# "features": {
# "due_dates": {"enabled": true},
# "time_tracking": {"enabled": true}
# }
# }
# ]
# }
Get space details.
Parameters:
space_id
(string, required): Space ID
Example:
space = await get_space(space_id="456")
List folders in a space.
Parameters:
space_id
(string, required): Space IDarchived
(bool, optional): Include archived (default: false)
Example:
folders = await list_folders(space_id="456")
# Returns:
# {
# "folders": [
# {
# "id": "789",
# "name": "Q4 Campaigns",
# "orderindex": 0,
# "task_count": 15,
# "lists": [...]
# }
# ]
# }
List lists in a folder or space.
Parameters:
folder_id
(string, optional): Folder IDspace_id
(string, optional): Space ID (for folderless lists)archived
(bool, optional): Include archived (default: false)
Example:
# Lists in a folder
lists = await list_lists(folder_id="789")
# Folderless lists in a space
lists = await list_lists(space_id="456")
# Returns:
# {
# "lists": [
# {
# "id": "abc123",
# "name": "Sprint Backlog",
# "orderindex": 0,
# "status": {
# "status": "active",
# "color": "#87909e"
# },
# "task_count": 42
# }
# ]
# }
List tasks with filters.
Parameters:
list_id
(string, required): List IDarchived
(bool, optional): Include archived (default: false)page
(int, optional): Page number (default: 0)order_by
(string, optional): Order by (created, updated, due_date)reverse
(bool, optional): Reverse order (default: true)subtasks
(bool, optional): Include subtasks (default: true)statuses
(list, optional): Filter by status namesinclude_closed
(bool, optional): Include closed tasks (default: false)assignees
(list, optional): Filter by assignee user IDstags
(list, optional): Filter by tag namesdue_date_gt
(int, optional): Due date greater than (Unix ms)due_date_lt
(int, optional): Due date less than (Unix ms)
Example:
# All tasks in a list
tasks = await list_tasks(list_id="abc123")
# Open tasks assigned to specific user
tasks = await list_tasks(
list_id="abc123",
assignees=["12345"],
include_closed=False
)
# Tasks due this week
import time
now = int(time.time() * 1000)
week_later = now + (7 * 24 * 60 * 60 * 1000)
tasks = await list_tasks(
list_id="abc123",
due_date_gt=now,
due_date_lt=week_later
)
# Tasks with specific status
tasks = await list_tasks(
list_id="abc123",
statuses=["in progress", "review"]
)
Get task details with custom fields.
Parameters:
task_id
(string, required): Task ID
Example:
task = await get_task(task_id="xyz789")
# Returns:
# {
# "id": "xyz789",
# "name": "Implement user authentication",
# "description": "Add OAuth 2.0 support",
# "status": {
# "status": "in progress",
# "color": "#d3d3d3"
# },
# "orderindex": "1.00",
# "date_created": "1633024800000",
# "date_updated": "1633111200000",
# "date_closed": null,
# "creator": {"id": 123, "username": "user"},
# "assignees": [{"id": 456, "username": "dev"}],
# "tags": [{"name": "backend", "tag_fg": "#000", "tag_bg": "#FFF"}],
# "parent": null,
# "priority": 2,
# "due_date": "1633197600000",
# "start_date": "1633024800000",
# "time_estimate": 7200000,
# "time_spent": 3600000,
# "custom_fields": [...],
# "list": {"id": "abc123", "name": "Sprint"},
# "folder": {"id": "789", "name": "Engineering"},
# "space": {"id": "456"},
# "url": "https://app.clickup.com/t/xyz789"
# }
Create a new task.
Parameters:
list_id
(string, required): List IDname
(string, required): Task namedescription
(string, optional): Task descriptionassignees
(list, optional): Assignee user IDstags
(list, optional): Tag namesstatus
(string, optional): Status namepriority
(int, optional): Priority (1=urgent, 2=high, 3=normal, 4=low)due_date
(int, optional): Due date (Unix timestamp ms)due_date_time
(bool, optional): Include time (default: false)time_estimate
(int, optional): Time estimate in millisecondsstart_date
(int, optional): Start date (Unix timestamp ms)start_date_time
(bool, optional): Include time (default: false)notify_all
(bool, optional): Notify assignees (default: true)parent
(string, optional): Parent task ID (for subtasks)custom_fields
(list, optional): Custom field objects
Priority Levels:
- 1: Urgent (red flag)
- 2: High (yellow flag)
- 3: Normal (blue flag, default)
- 4: Low (gray flag)
Example:
# Simple task
task = await create_task(
list_id="abc123",
name="Write API documentation"
)
# Full task with all options
import time
tomorrow = int((time.time() + 86400) * 1000)
task = await create_task(
list_id="abc123",
name="Deploy to production",
description="Deploy version 2.0.0 with new features",
assignees=[12345, 67890],
tags=["deployment", "urgent"],
status="todo",
priority=1,
due_date=tomorrow,
due_date_time=True,
time_estimate=3600000, # 1 hour in ms
notify_all=True
)
# Subtask
subtask = await create_task(
list_id="abc123",
name="Run database migrations",
parent="xyz789", # Parent task ID
assignees=[12345],
priority=2
)
# Task with custom fields
task = await create_task(
list_id="abc123",
name="Bug fix: Login issue",
custom_fields=[
{"id": "field_123", "value": "Bug"},
{"id": "field_456", "value": "High"}
]
)
Update task details.
Parameters:
task_id
(string, required): Task IDname
(string, optional): Updated namedescription
(string, optional): Updated descriptionstatus
(string, optional): Updated statuspriority
(int, optional): Updated priority (1-4)due_date
(int, optional): Updated due date (Unix ms)time_estimate
(int, optional): Updated time estimate (ms)assignees
(dict, optional): Assignees {"add": [ids], "rem": [ids]}
Example:
# Update task status
task = await update_task(
task_id="xyz789",
status="in progress"
)
# Add assignees
task = await update_task(
task_id="xyz789",
assignees={"add": [12345, 67890]}
)
# Remove assignees
task = await update_task(
task_id="xyz789",
assignees={"rem": [12345]}
)
# Update priority and due date
import time
next_week = int((time.time() + 7 * 86400) * 1000)
task = await update_task(
task_id="xyz789",
priority=1,
due_date=next_week
)
Delete a task.
Parameters:
task_id
(string, required): Task ID
Example:
result = await delete_task(task_id="xyz789")
Add comment to a task.
Parameters:
task_id
(string, required): Task IDcomment_text
(string, required): Comment textassignee
(int, optional): Assign comment to user IDnotify_all
(bool, optional): Notify all assignees (default: true)
Example:
# Simple comment
comment = await add_task_comment(
task_id="xyz789",
comment_text="Great progress on this task!"
)
# Comment with assignment
comment = await add_task_comment(
task_id="xyz789",
comment_text="Can you review this?",
assignee=12345,
notify_all=True
)
Get task comments.
Parameters:
task_id
(string, required): Task ID
Example:
comments = await list_task_comments(task_id="xyz789")
# Returns:
# {
# "comments": [
# {
# "id": "123",
# "comment_text": "Great work!",
# "user": {"id": 456, "username": "user"},
# "date": "1633024800000"
# }
# ]
# }
Track time on a task.
Parameters:
task_id
(string, required): Task IDduration
(int, required): Duration in millisecondsstart
(int, optional): Start time (Unix ms, defaults to now)description
(string, optional): Time entry description
Example:
# Log 2 hours of work
two_hours_ms = 2 * 60 * 60 * 1000
time_entry = await create_time_entry(
task_id="xyz789",
duration=two_hours_ms,
description="Implemented OAuth integration"
)
# Log time with specific start time
import time
start_time = int((time.time() - 7200) * 1000) # 2 hours ago
time_entry = await create_time_entry(
task_id="xyz789",
duration=two_hours_ms,
start=start_time
)
Get time tracking entries.
Parameters:
workspace_id
(string, required): Workspace IDstart_date
(int, optional): Filter by start date (Unix ms)end_date
(int, optional): Filter by end date (Unix ms)assignee
(int, optional): Filter by assignee user ID
Example:
# All time entries
entries = await list_time_entries(workspace_id="123")
# Time entries for this week
import time
week_ago = int((time.time() - 7 * 86400) * 1000)
now = int(time.time() * 1000)
entries = await list_time_entries(
workspace_id="123",
start_date=week_ago,
end_date=now
)
# Time entries for specific user
entries = await list_time_entries(
workspace_id="123",
assignee=12345
)
List goals in a workspace.
Parameters:
workspace_id
(string, required): Workspace ID
Example:
goals = await list_goals(workspace_id="123")
# Returns:
# {
# "goals": [
# {
# "id": "goal_123",
# "name": "Q4 Revenue Target",
# "due_date": "1640995200000",
# "description": "Reach $1M ARR",
# "percent_completed": 75,
# "color": "#32a852"
# }
# ]
# }
Get goal details and progress.
Parameters:
goal_id
(string, required): Goal ID
Example:
goal = await get_goal(goal_id="goal_123")
# Returns:
# {
# "goal": {
# "id": "goal_123",
# "name": "Q4 Revenue Target",
# "description": "Reach $1M ARR",
# "due_date": "1640995200000",
# "color": "#32a852",
# "percent_completed": 75,
# "key_results": [
# {
# "id": "kr_456",
# "name": "Close 10 enterprise deals",
# "type": "number",
# "current": 7,
# "target": 10,
# "percent_completed": 70
# }
# ]
# }
# }
Get custom fields for a list.
Parameters:
list_id
(string, required): List ID
Example:
fields = await list_custom_fields(list_id="abc123")
# Returns:
# {
# "fields": [
# {
# "id": "field_123",
# "name": "Priority",
# "type": "drop_down",
# "type_config": {
# "options": [
# {"id": "opt_1", "name": "High", "color": "#FF0000"},
# {"id": "opt_2", "name": "Low", "color": "#00FF00"}
# ]
# }
# },
# {
# "id": "field_456",
# "name": "Story Points",
# "type": "number"
# }
# ]
# }
Custom Field Types:
text
: Text inputnumber
: Numeric inputdrop_down
: Dropdown selectiondate
: Date pickercheckbox
: Boolean checkboxurl
: URL inputemail
: Email inputphone
: Phone numbercurrency
: Currency value
Search tasks across workspace.
Parameters:
workspace_id
(string, required): Workspace IDquery
(string, required): Search query textstart_date
(int, optional): Filter by start date (Unix ms)end_date
(int, optional): Filter by end date (Unix ms)assignees
(list, optional): Filter by assignee user IDsstatuses
(list, optional): Filter by status namestags
(list, optional): Filter by tag names
Example:
# Search by keyword
tasks = await search_tasks(
workspace_id="123",
query="authentication"
)
# Advanced search with filters
tasks = await search_tasks(
workspace_id="123",
query="bug",
assignees=[12345],
statuses=["in progress"],
tags=["urgent"]
)
# 1. Get workspace
workspaces = await list_workspaces()
workspace_id = workspaces["teams"][0]["id"]
# 2. Create or get space
spaces = await list_spaces(workspace_id=workspace_id)
space_id = spaces["spaces"][0]["id"]
# 3. Get lists
lists = await list_lists(space_id=space_id)
list_id = lists["lists"][0]["id"]
# 4. Create tasks
await create_task(
list_id=list_id,
name="Set up development environment",
priority=2
)
await create_task(
list_id=list_id,
name="Write technical documentation",
priority=3
)
# Get all tasks
tasks = await list_tasks(list_id="abc123")
# Sort by priority
sorted_tasks = sorted(
tasks["tasks"],
key=lambda t: t.get("priority", 3)
)
# Assign to team members
team_members = [12345, 67890, 11111]
for i, task in enumerate(sorted_tasks[:10]):
assignee = team_members[i % len(team_members)]
await update_task(
task_id=task["id"],
assignees={"add": [assignee]},
status="todo"
)
import time
# Get this week's time entries
week_ago = int((time.time() - 7 * 86400) * 1000)
now = int(time.time() * 1000)
entries = await list_time_entries(
workspace_id="123",
start_date=week_ago,
end_date=now
)
# Calculate total hours
total_ms = sum(entry["duration"] for entry in entries["data"])
total_hours = total_ms / (1000 * 60 * 60)
print(f"Total hours this week: {total_hours:.2f}")
# List all goals
goals = await list_goals(workspace_id="123")
# Check progress on each goal
for goal in goals["goals"]:
goal_detail = await get_goal(goal_id=goal["id"])
print(f"Goal: {goal_detail['goal']['name']}")
print(f"Progress: {goal_detail['goal']['percent_completed']}%")
for kr in goal_detail['goal']['key_results']:
print(f" - {kr['name']}: {kr['current']}/{kr['target']}")
- Use hierarchy effectively: Organize with Spaces → Folders → Lists
- Custom statuses: Set up workflows per list
- Custom fields: Add metadata for filtering and reporting
- Time tracking: Log time regularly for accurate estimates
- Tags: Use tags for cross-list categorization
- Goals: Set measurable goals with key results
- Priorities: Use priority levels consistently
- Assignees: Assign tasks for accountability
- Comments: Communicate within tasks
- Search: Use search for cross-workspace queries
import asyncio
async def make_request_with_retry():
try:
return await list_tasks(list_id="abc123")
except httpx.HTTPStatusError as e:
if e.response.status_code == 429:
# Wait and retry
await asyncio.sleep(60)
return await list_tasks(list_id="abc123")
raise
Common errors:
- 401 Unauthorized: Invalid API token
- 403 Forbidden: Insufficient permissions
- 404 Not Found: Resource not found
- 429 Too Many Requests: Rate limit exceeded (100 req/min)
- 500 Internal Server Error: ClickUp service issue