import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { MCP } from "./mcp.js";
export async function setupServer() {
const server = new McpServer({
name: "clickup-mcp",
version: "1.0.0",
});
server.tool(
"get_tasks",
"Get tasks from a ClickUp list",
{
listId: z.string().describe("The ID of the list to fetch tasks from"),
},
async ({ listId }) => {
try {
const mcp = new MCP();
const tasks = await mcp.getTasks(listId);
return {
content: [
{
type: "text",
text: JSON.stringify(tasks, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}. Please ensure you are logged in using 'clickup-mcp login'.`,
},
],
isError: true,
};
}
}
);
server.tool(
"create_task",
"Create a new task in a list",
{
listId: z.string().describe("The ID of the list to create the task in"),
name: z.string().describe("The name of the task"),
description: z.string().optional().describe("The description of the task"),
},
async ({ listId, name, description }) => {
try {
const mcp = new MCP();
const task = await mcp.createTask(listId, name, description);
return {
content: [{ type: "text", text: JSON.stringify(task, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"update_task",
"Update an existing task",
{
taskId: z.string().describe("The ID of the task to update"),
data: z.string().describe("JSON string of fields to update"),
},
async ({ taskId, data }) => {
try {
const mcp = new MCP();
const updateData = JSON.parse(data);
const task = await mcp.updateTask(taskId, updateData);
return {
content: [{ type: "text", text: JSON.stringify(task, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_task",
"Get details of a specific task",
{
taskId: z.string().describe("The ID of the task"),
},
async ({ taskId }) => {
try {
const mcp = new MCP();
const task = await mcp.getTask(taskId);
return {
content: [{ type: "text", text: JSON.stringify(task, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_comments",
"Get comments for a task",
{
taskId: z.string().describe("The ID of the task"),
},
async ({ taskId }) => {
try {
const mcp = new MCP();
const comments = await mcp.getComments(taskId);
return {
content: [{ type: "text", text: JSON.stringify(comments, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"create_comment",
"Add a comment to a task",
{
taskId: z.string().describe("The ID of the task"),
comment: z.string().describe("The comment text"),
},
async ({ taskId, comment }) => {
try {
const mcp = new MCP();
const result = await mcp.createComment(taskId, comment);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_time_entries",
"Get time entries",
{
taskId: z.string().optional().describe("Filter by task ID"),
},
async ({ taskId }) => {
try {
const mcp = new MCP();
const entries = await mcp.getTimeEntries(taskId);
return {
content: [{ type: "text", text: JSON.stringify(entries, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"start_timer",
"Start tracking time for a task",
{
taskId: z.string().describe("The ID of the task"),
},
async ({ taskId }) => {
try {
const mcp = new MCP();
const result = await mcp.startTimer(taskId);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"stop_timer",
"Stop tracking time",
{},
async () => {
try {
const mcp = new MCP();
const result = await mcp.stopTimer();
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_workspaces",
"Get available workspaces",
{},
async () => {
try {
const mcp = new MCP();
const workspaces = await mcp.getWorkspaces();
return {
content: [{ type: "text", text: JSON.stringify(workspaces, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_spaces",
"Get spaces in a workspace",
{
workspaceId: z.string().describe("The ID of the workspace"),
},
async ({ workspaceId }) => {
try {
const mcp = new MCP();
const spaces = await mcp.getSpaces(workspaceId);
return {
content: [{ type: "text", text: JSON.stringify(spaces, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_folders",
"Get folders in a space",
{
spaceId: z.string().describe("The ID of the space"),
},
async ({ spaceId }) => {
try {
const mcp = new MCP();
const folders = await mcp.getFolders(spaceId);
return {
content: [{ type: "text", text: JSON.stringify(folders, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_lists",
"Get lists in a folder",
{
folderId: z.string().describe("The ID of the folder"),
},
async ({ folderId }) => {
try {
const mcp = new MCP();
const lists = await mcp.getLists(folderId);
return {
content: [{ type: "text", text: JSON.stringify(lists, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_space_lists",
"Get lists that are not in a folder (at the space level)",
{
spaceId: z.string().describe("The ID of the space"),
},
async ({ spaceId }) => {
try {
const mcp = new MCP();
const lists = await mcp.getSpaceLists(spaceId);
return {
content: [{ type: "text", text: JSON.stringify(lists, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_list_details",
"Get full configuration of a list (statuses, defaults, etc)",
{
listId: z.string().describe("The ID of the list"),
},
async ({ listId }) => {
try {
const mcp = new MCP();
const list = await mcp.getList(listId);
return {
content: [{ type: "text", text: JSON.stringify(list, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"find_list",
"Find a list by name across the entire workspace (Global Discovery)",
{
workspaceId: z.string().describe("The ID of the workspace (Team)"),
name: z.string().describe("The name or partial name of the list to find"),
},
async ({ workspaceId, name }) => {
try {
const mcp = new MCP();
const matches = await mcp.findListByName(workspaceId, name);
return {
content: [{ type: "text", text: JSON.stringify(matches, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_accessible_custom_fields",
"Get custom fields configured for a list",
{
listId: z.string().describe("The ID of the list"),
},
async ({ listId }) => {
try {
const mcp = new MCP();
const fields = await mcp.getAccessibleCustomFields(listId);
return {
content: [{ type: "text", text: JSON.stringify(fields, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"create_custom_field",
"Create a new custom field on a list",
{
listId: z.string().describe("The ID of the list"),
name: z.string().describe("The name of the custom field"),
type: z.string().describe("The type of the custom field (e.g. 'short_text', 'number', 'currency', 'date')"),
type_config: z.any().optional().describe("Optional configuration for the custom field type"),
},
async ({ listId, name, type, type_config }) => {
try {
const mcp = new MCP();
const field = await mcp.createCustomField(listId, name, type, type_config);
return {
content: [{ type: "text", text: JSON.stringify(field, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
server.tool(
"get_current_user",
"Get the currently authenticated user",
{},
async () => {
try {
const mcp = new MCP();
const user = await mcp.getCurrentUser();
return {
content: [{ type: "text", text: JSON.stringify(user, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
// Space Management
server.tool(
"create_space",
"Create a new space",
{
workspaceId: z.string(),
name: z.string(),
},
async ({ workspaceId, name }) => {
try {
const mcp = new MCP();
const res = await mcp.createSpace(workspaceId, name);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"update_space",
"Update a space",
{
spaceId: z.string(),
name: z.string(),
},
async ({ spaceId, name }) => {
try {
const mcp = new MCP();
const res = await mcp.updateSpace(spaceId, name);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"delete_space",
"Delete a space",
{
spaceId: z.string(),
},
async ({ spaceId }) => {
try {
const mcp = new MCP();
const res = await mcp.deleteSpace(spaceId);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
// Folder Management
server.tool(
"create_folder",
"Create a new folder",
{
spaceId: z.string(),
name: z.string(),
},
async ({ spaceId, name }) => {
try {
const mcp = new MCP();
const res = await mcp.createFolder(spaceId, name);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"update_folder",
"Update a folder",
{
folderId: z.string(),
name: z.string(),
},
async ({ folderId, name }) => {
try {
const mcp = new MCP();
const res = await mcp.updateFolder(folderId, name);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"delete_folder",
"Delete a folder",
{
folderId: z.string(),
},
async ({ folderId }) => {
try {
const mcp = new MCP();
const res = await mcp.deleteFolder(folderId);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
// List Management
server.tool(
"create_list_in_folder",
"Create a list in a folder",
{
folderId: z.string(),
name: z.string(),
},
async ({ folderId, name }) => {
try {
const mcp = new MCP();
const res = await mcp.createListInFolder(folderId, name);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"create_list_in_space",
"Create a list in a space",
{
spaceId: z.string(),
name: z.string(),
},
async ({ spaceId, name }) => {
try {
const mcp = new MCP();
const res = await mcp.createListInSpace(spaceId, name);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"update_list",
"Update a list",
{
listId: z.string(),
name: z.string(),
},
async ({ listId, name }) => {
try {
const mcp = new MCP();
const res = await mcp.updateList(listId, name);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"delete_list",
"Delete a list",
{
listId: z.string(),
},
async ({ listId }) => {
try {
const mcp = new MCP();
const res = await mcp.deleteList(listId);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
// Tag Management
server.tool(
"get_tags",
"Get tags in a space",
{ spaceId: z.string() },
async ({ spaceId }) => {
try {
const mcp = new MCP();
const res = await mcp.getTags(spaceId);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"create_tag",
"Create a tag in a space",
{
spaceId: z.string(),
name: z.string(),
tag_bg: z.string().optional(),
tag_fg: z.string().optional(),
},
async ({ spaceId, name, tag_bg, tag_fg }) => {
try {
const mcp = new MCP();
const res = await mcp.createTag(spaceId, name, tag_bg, tag_fg);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"edit_tag",
"Edit a tag",
{
spaceId: z.string(),
tagName: z.string(),
newName: z.string(),
tag_bg: z.string().optional(),
tag_fg: z.string().optional(),
},
async ({ spaceId, tagName, newName, tag_bg, tag_fg }) => {
try {
const mcp = new MCP();
const res = await mcp.editTag(spaceId, tagName, newName, tag_bg, tag_fg);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"delete_tag",
"Delete a tag",
{
spaceId: z.string(),
tagName: z.string(),
},
async ({ spaceId, tagName }) => {
try {
const mcp = new MCP();
const res = await mcp.deleteTag(spaceId, tagName);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"add_tag_to_task",
"Add tag to task",
{
taskId: z.string(),
tagName: z.string(),
},
async ({ taskId, tagName }) => {
try {
const mcp = new MCP();
const res = await mcp.addTagToTask(taskId, tagName);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"remove_tag_from_task",
"Remove tag from task",
{
taskId: z.string(),
tagName: z.string(),
},
async ({ taskId, tagName }) => {
try {
const mcp = new MCP();
const res = await mcp.removeTagFromTask(taskId, tagName);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
// Member & Group Management
server.tool(
"get_workspace_members",
"Get members of a workspace",
{ workspaceId: z.string() },
async ({ workspaceId }) => {
try {
const mcp = new MCP();
const res = await mcp.getWorkspaceMembers(workspaceId);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"get_user_groups",
"Get user groups (teams) in a workspace",
{ workspaceId: z.string() },
async ({ workspaceId }) => {
try {
const mcp = new MCP();
const res = await mcp.getUserGroups(workspaceId);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"create_user_group",
"Create a user group",
{
workspaceId: z.string(),
name: z.string(),
memberIds: z.array(z.string()).describe("List of user IDs (integers as strings)")
},
async ({ workspaceId, name, memberIds }) => {
try {
const mcp = new MCP();
const res = await mcp.createUserGroup(workspaceId, name, memberIds);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"update_user_group",
"Update a user group",
{
groupId: z.string(),
name: z.string(),
memberIds: z.array(z.string())
},
async ({ groupId, name, memberIds }) => {
try {
const mcp = new MCP();
const res = await mcp.updateUserGroup(groupId, name, memberIds);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"delete_user_group",
"Delete a user group",
{ groupId: z.string() },
async ({ groupId }) => {
try {
const mcp = new MCP();
const res = await mcp.deleteUserGroup(groupId);
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
);
server.tool(
"search",
"Search ClickUp resources",
{
query: z.string().describe("The search query"),
workspaceId: z.string().describe("The workspace to search in"),
},
async ({ query, workspaceId }) => {
try {
const mcp = new MCP();
const results = await mcp.search(query, workspaceId);
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
return server;
}
export async function startServer() {
const server = await setupServer();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("ClickUp MCP Server running on stdio");
}