index.ts•7.65 kB
#!/usr/bin/env node
// @ts-ignore
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
// @ts-ignore
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// @ts-ignore
import { InitializeRequestSchema, CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import axios from 'axios';
const ACCESS_TOKEN = process.env.CLICKUP_ACCESS_TOKEN;
if (!ACCESS_TOKEN) {
throw new Error('CLICKUP_ACCESS_TOKEN environment variable is required');
}
// Create axios instance for ClickUp API
const clickupApi = axios.create({
baseURL: 'https://api.clickup.com/api/v2',
headers: {
'Authorization': ACCESS_TOKEN,
'Content-Type': 'application/json'
},
});
// Define capabilities
const capabilities = {
tools: {
get_tasks: {
description: "Get tasks from a ClickUp list",
inputSchema: {
type: "object",
properties: {
list_id: {
type: "string",
description: "ClickUp List ID"
},
limit: {
type: "number",
description: "Number of tasks to retrieve (max 100)",
default: 50,
minimum: 1,
maximum: 100
}
},
required: ["list_id"]
}
},
create_task: {
description: "Create a new task in a ClickUp list",
inputSchema: {
type: "object",
properties: {
list_id: {
type: "string",
description: "ClickUp List ID"
},
name: {
type: "string",
description: "Task name"
},
description: {
type: "string",
description: "Task description"
},
assignees: {
type: "array",
items: { type: "number" },
description: "Array of assignee user IDs"
},
due_date: {
type: "string",
description: "Due date in Unix timestamp (milliseconds)"
}
},
required: ["list_id", "name"]
}
},
update_task: {
description: "Update an existing ClickUp task",
inputSchema: {
type: "object",
properties: {
task_id: {
type: "string",
description: "ClickUp Task ID"
},
name: {
type: "string",
description: "New task name"
},
description: {
type: "string",
description: "New task description"
},
status: {
type: "string",
description: "New status"
},
assignees: {
type: "array",
items: { type: "number" },
description: "New array of assignee user IDs"
},
due_date: {
type: "string",
description: "New due date in Unix timestamp (milliseconds)"
}
},
required: ["task_id"]
}
},
get_task: {
description: "Get details of a ClickUp task",
inputSchema: {
type: "object",
properties: {
task_id: {
type: "string",
description: "ClickUp Task ID"
}
},
required: ["task_id"]
}
}
}
};
// Create an MCP server
const server = new Server({
name: "clickup-server",
version: "0.1.0"
}, {
capabilities: {
tools: {}
}
});
// Tool handlers
const getTasks = async (args: any) => {
try {
const response = await clickupApi.get(`/list/${args.list_id}/task`, {
params: {
limit: args.limit || 50,
},
});
return {
content: [
{
type: "text",
text: JSON.stringify(response.data.tasks, null, 2),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: "text",
text: `ClickUp API error: ${error.response?.data?.err ?? error.message}`,
},
],
isError: true,
};
}
throw error;
}
};
const createTask = async (args: any) => {
try {
const taskData: any = {
name: args.name,
};
if (args.description) taskData.description = args.description;
if (args.assignees) taskData.assignees = args.assignees;
if (args.due_date) taskData.due_date = parseInt(args.due_date);
const response = await clickupApi.post(`/list/${args.list_id}/task`, taskData);
return {
content: [
{
type: "text",
text: JSON.stringify(response.data, null, 2),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: "text",
text: `ClickUp API error: ${error.response?.data?.err ?? error.message}`,
},
],
isError: true,
};
}
throw error;
}
};
const updateTask = async (args: any) => {
try {
const updateData: any = {};
if (args.name) updateData.name = args.name;
if (args.description !== undefined) updateData.description = args.description;
if (args.status) updateData.status = args.status;
if (args.assignees) updateData.assignees = args.assignees;
if (args.due_date) updateData.due_date = parseInt(args.due_date);
const response = await clickupApi.put(`/task/${args.task_id}`, updateData);
return {
content: [
{
type: "text",
text: JSON.stringify(response.data, null, 2),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: "text",
text: `ClickUp API error: ${error.response?.data?.err ?? error.message}`,
},
],
isError: true,
};
}
throw error;
}
};
const getTask = async (args: any) => {
try {
const response = await clickupApi.get(`/task/${args.task_id}`);
return {
content: [
{
type: "text",
text: JSON.stringify(response.data, null, 2),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: "text",
text: `ClickUp API error: ${error.response?.data?.err ?? error.message}`,
},
],
isError: true,
};
}
throw error;
}
};
// Set request handlers
// @ts-ignore
server.setRequestHandler(InitializeRequestSchema, async (request) => {
// @ts-ignore
return {
// @ts-ignore
protocolVersion: request.params.protocolVersion,
capabilities,
serverInfo: {
name: "clickup-server",
version: "0.1.0"
}
};
});
// @ts-ignore
server.setRequestHandler(CallToolRequestSchema, async (request) => {
// @ts-ignore
const { name, arguments: args } = request.params;
switch (name) {
case 'get_tasks':
return await getTasks(args);
case 'create_task':
return await createTask(args);
case 'update_task':
return await updateTask(args);
case 'get_task':
return await getTask(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// @ts-ignore
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: Object.entries(capabilities.tools).map(([name, tool]) => ({
name,
...tool
}))
};
});
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('ClickUp MCP server running on stdio');