@s.pawel940/clickup-mcp-server
Version:
ClickUp MCP Server (Read-Only Fork) - Integrate ClickUp tasks with AI through Model Context Protocol
196 lines (195 loc) • 7.98 kB
JavaScript
/**
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
* SPDX-License-Identifier: MIT
*
* MCP Server for ClickUp integration
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
import config from "./config.js";
import { workspaceHierarchyTool, handleGetWorkspaceHierarchy } from "./tools/workspace.js";
import { getTaskTool, getTaskCommentsTool, getWorkspaceTasksTool, getTaskTimeEntriesTool, getCurrentTimeEntryTool, handleGetTaskComments, handleGetTask, handleGetWorkspaceTasks, handleGetTaskTimeEntries, handleGetCurrentTimeEntry } from "./tools/task/index.js";
import { getListTool, handleGetList, } from "./tools/list.js";
import { getFolderTool, handleGetFolder, } from "./tools/folder.js";
import { getSpaceTagsTool, handleGetSpaceTags, } from "./tools/tag.js";
import { getDocumentTool, listDocumentsTool, listDocumentPagesTool, getDocumentPagesTool, } from "./tools/documents.js";
import { getWorkspaceMembersTool, handleGetWorkspaceMembers, findMemberByNameTool, handleFindMemberByName, resolveAssigneesTool, handleResolveAssignees } from "./tools/member.js";
import { Logger } from "./logger.js";
import { clickUpServices } from "./services/shared.js";
// Create a logger instance for server
const logger = new Logger('Server');
// Use existing services from shared module instead of creating new ones
const { workspace } = clickUpServices;
export const server = new Server({
name: "clickup-mcp-server",
version: "0.7.2",
}, {
capabilities: {
tools: {},
prompts: {},
resources: {},
},
});
const documentModule = () => {
if (config.documentSupport === 'true') {
return [
getDocumentTool,
listDocumentsTool,
listDocumentPagesTool,
getDocumentPagesTool,
];
}
else {
return [];
}
};
/**
* Configure the server routes and handlers
*/
export function configureServer() {
logger.info("Registering server request handlers");
logger.info(`Server readOnlyMode is set to: ${config.readOnlyMode}`);
// Register ListTools handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
logger.debug("Received ListTools request");
// Define read-only tools
const tools = [
workspaceHierarchyTool,
getTaskTool,
getTaskCommentsTool,
getWorkspaceTasksTool,
getTaskTimeEntriesTool,
getCurrentTimeEntryTool,
getListTool,
getFolderTool,
getSpaceTagsTool,
getWorkspaceMembersTool,
findMemberByNameTool,
resolveAssigneesTool,
...(config.documentSupport === 'true' ? [
getDocumentTool,
listDocumentsTool,
listDocumentPagesTool,
getDocumentPagesTool
] : [])
];
return {
tools: tools.filter(tool => !config.disabledTools.includes(tool.name))
};
});
// Add handler for resources/list
server.setRequestHandler(ListResourcesRequestSchema, async (req) => {
logger.debug("Received ListResources request");
return { resources: [] };
});
// Register CallTool handler with proper logging
logger.info("Registering tool handlers", {
toolCount: 12, // Updated count for read-only tools
categories: ["workspace", "task", "time-tracking", "list", "folder", "tag", "document", "member"]
});
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const { name, arguments: params } = req.params;
// Improved logging with more context
logger.info(`Received CallTool request for tool: ${name}`, {
params
});
// Check if the tool is disabled or not a read-only tool
const readOnlyToolNames = [
workspaceHierarchyTool.name,
getTaskTool.name,
getTaskCommentsTool.name,
getWorkspaceTasksTool.name,
getTaskTimeEntriesTool.name,
getCurrentTimeEntryTool.name,
getListTool.name,
getFolderTool.name,
getSpaceTagsTool.name,
getWorkspaceMembersTool.name,
findMemberByNameTool.name,
resolveAssigneesTool.name,
...(config.documentSupport === 'true' ? [
getDocumentTool.name,
listDocumentsTool.name,
listDocumentPagesTool.name,
getDocumentPagesTool.name
] : [])
];
if (config.disabledTools.includes(name) || !readOnlyToolNames.includes(name)) {
logger.warn(`Tool execution blocked: Tool '${name}' is disabled or not allowed in read-only mode.`);
throw {
code: -32601,
message: `Tool '${name}' is disabled or not allowed in read-only mode.`
};
}
try {
// Handle tool calls by routing to the appropriate handler
switch (name) {
case "get_workspace_hierarchy":
return handleGetWorkspaceHierarchy();
case "get_task":
return handleGetTask(params);
case "get_task_comments":
return handleGetTaskComments(params);
case "get_workspace_tasks":
return handleGetWorkspaceTasks(params);
case "get_task_time_entries":
return handleGetTaskTimeEntries(params);
case "get_current_time_entry":
return handleGetCurrentTimeEntry(params);
case "get_list":
return handleGetList(params);
case "get_folder":
return handleGetFolder(params);
case "get_space_tags":
return handleGetSpaceTags(params);
case "get_workspace_members":
return handleGetWorkspaceMembers();
case "find_member_by_name":
return handleFindMemberByName(params);
case "resolve_assignees":
return handleResolveAssignees(params);
default:
logger.error(`Unknown tool requested: ${name}`);
const error = new Error(`Unknown tool: ${name}`);
error.name = "UnknownToolError";
throw error;
}
}
catch (err) {
logger.error(`Error executing tool: ${name}`, err);
// Transform error to a more descriptive JSON-RPC error
if (err.name === "UnknownToolError") {
throw {
code: -32601,
message: `Method not found: ${name}`
};
}
else if (err.name === "ValidationError") {
throw {
code: -32602,
message: `Invalid params for tool ${name}: ${err.message}`
};
}
else {
// Generic server error
throw {
code: -32000,
message: `Error executing tool ${name}: ${err.message}`
};
}
}
});
server.setRequestHandler(ListPromptsRequestSchema, async () => {
logger.info("Received ListPrompts request");
return { prompts: [] };
});
server.setRequestHandler(GetPromptRequestSchema, async () => {
logger.error("Received GetPrompt request, but prompts are not supported");
throw new Error("Prompt not found");
});
return server;
}
/**
* Export the clickup service for use in tool handlers
*/
export { workspace };