Skip to main content
Glama
server.ts8.3 kB
/** * Developed by eBrook Group. * Copyright © 2026 eBrook Group (https://www.ebrook.com.tw) */ /** * MCP Server setup and tool registration */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { getConfig } from "./config/env.js"; import { ClickUpService } from "./services/clickup.js"; import { handleGetTaskDetails } from "./tools/get-task-details.js"; import { handleAnalyzeWorkflow } from "./tools/analyze-workflow.js"; import { handleUpdateStatus } from "./tools/update-status.js"; import { handleUpdateGithubBranch } from "./tools/update-github-branch.js"; import { handleAddComment } from "./tools/add-comment.js"; import { debug, info } from "./utils/logger.js"; /** * Start the MCP server * @returns Promise that resolves when server is started */ export async function startServer(): Promise<void> { // Load configuration const config = getConfig(); // Initialize services const clickUpService = new ClickUpService(config); // Create MCP server instance const server = new McpServer({ name: "clickup-mcp", version: "3.1.0", }); // Register the get_task_details tool server.tool( "get_task_details", "Get detailed information about a ClickUp task, including status, assignees, tags, custom fields, and more.", { task_id: z .string() .min(1, "Task ID cannot be empty") .max(100, "Task ID exceeds maximum length of 100 characters") .describe("ClickUp task ID (can be custom ID like ST2IN1-123 or numeric ID)"), }, async (args) => { debug(`Tool 'get_task_details' called with args: ${JSON.stringify(args)}`); const taskId = args.task_id; if (!taskId || typeof taskId !== "string" || taskId.trim().length === 0) { return { content: [ { type: "text", text: "Error: task_id is required and cannot be empty.", }, ], isError: true, }; } return await handleGetTaskDetails(taskId.trim(), config, clickUpService); } ); // Register the analyze_workflow tool server.tool( "analyze_workflow", "Analyze a ClickUp task's workflow, including time tracking, dependencies, risk factors, and provide recommendations for improvement.", { task_id: z .string() .min(1, "Task ID cannot be empty") .max(100, "Task ID exceeds maximum length of 100 characters") .describe("ClickUp task ID (can be custom ID like ST2IN1-123 or numeric ID)"), }, async (args) => { debug(`Tool 'analyze_workflow' called with args: ${JSON.stringify(args)}`); const taskId = args.task_id; if (!taskId || typeof taskId !== "string" || taskId.trim().length === 0) { return { content: [ { type: "text", text: "Error: task_id is required and cannot be empty.", }, ], isError: true, }; } return await handleAnalyzeWorkflow(taskId.trim(), config, clickUpService); } ); // Register the update_status tool server.tool( "update_status", "Update the status of a ClickUp task.", { task_id: z .string() .min(1, "Task ID cannot be empty") .max(100, "Task ID exceeds maximum length of 100 characters") .describe("ClickUp task ID (can be custom ID like ST2IN1-123 or numeric ID)"), status: z .string() .min(1, "Status cannot be empty") .max(100, "Status exceeds maximum length of 100 characters") .regex(/^[a-z0-9_\s]+$/i, "Status contains invalid characters") .describe("The new status value to set for the task"), }, async (args) => { debug(`Tool 'update_status' called with args: ${JSON.stringify(args)}`); const taskId = args.task_id; const status = args.status; if (!taskId || typeof taskId !== "string" || taskId.trim().length === 0) { return { content: [ { type: "text", text: "Error: task_id is required and cannot be empty.", }, ], isError: true, }; } if (!status || typeof status !== "string" || status.trim().length === 0) { return { content: [ { type: "text", text: "Error: status is required and cannot be empty.", }, ], isError: true, }; } return await handleUpdateStatus(taskId.trim(), status.trim(), config, clickUpService); } ); // Register the update_github_branch tool server.tool( "update_github_branch", "Update the GitHub Branch custom field of a ClickUp task.", { task_id: z .string() .min(1, "Task ID cannot be empty") .max(100, "Task ID exceeds maximum length of 100 characters") .describe("ClickUp task ID (can be custom ID like ST2IN1-123 or numeric ID)"), branch_name: z .string() .min(1, "Branch name cannot be empty") .max(255, "Branch name exceeds maximum length of 255 characters") .regex(/^[a-zA-Z0-9/_-]+$/, "Branch name contains invalid characters") .describe("The GitHub branch name to set"), }, async (args) => { debug(`Tool 'update_github_branch' called with args: ${JSON.stringify(args)}`); const taskId = args.task_id; const branchName = args.branch_name; if (!taskId || typeof taskId !== "string" || taskId.trim().length === 0) { return { content: [ { type: "text", text: "Error: task_id is required and cannot be empty.", }, ], isError: true, }; } if (!branchName || typeof branchName !== "string" || branchName.trim().length === 0) { return { content: [ { type: "text", text: "Error: branch_name is required and cannot be empty.", }, ], isError: true, }; } return await handleUpdateGithubBranch(taskId.trim(), branchName.trim(), config, clickUpService); } ); // Register the add_comment tool server.tool( "add_comment", "Add a comment to a ClickUp task.", { task_id: z .string() .min(1, "Task ID cannot be empty") .max(100, "Task ID exceeds maximum length of 100 characters") .describe("ClickUp task ID (can be custom ID like ST2IN1-123 or numeric ID)"), comment_text: z .string() .min(1, "Comment cannot be empty") .max(10000, "Comment exceeds maximum length of 10000 characters") .describe("The comment text to add to the task"), }, async (args) => { debug(`Tool 'add_comment' called with args: ${JSON.stringify(args)}`); const taskId = args.task_id; const commentText = args.comment_text; if (!taskId || typeof taskId !== "string" || taskId.trim().length === 0) { return { content: [ { type: "text", text: "Error: task_id is required and cannot be empty.", }, ], isError: true, }; } if (!commentText || typeof commentText !== "string" || commentText.trim().length === 0) { return { content: [ { type: "text", text: "Error: comment_text is required and cannot be empty.", }, ], isError: true, }; } return await handleAddComment(taskId.trim(), commentText.trim(), config, clickUpService); } ); // Connect server to transport const transport = new StdioServerTransport(); await server.connect(transport); info("MCP ClickUp server started with 5 tools:"); info(" - get_task_details: View detailed task information"); info(" - analyze_workflow: Analyze task workflow and get insights"); info(" - update_status: Update task status"); info(" - update_github_branch: Update GitHub Branch field"); info(" - add_comment: Add a comment to a task"); }

Latest Blog Posts

MCP directory API

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

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tangbodie/clickup-mcp'

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