#
tokens: 13584/50000 25/25 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   └── workflows
│       └── pr_agent.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.MD
├── src
│   ├── controllers
│   │   ├── assignee.controller.ts
│   │   ├── custom-field.controller.ts
│   │   ├── docs.controller.ts
│   │   ├── folder.controller.ts
│   │   ├── list.controller.ts
│   │   ├── space.controller.ts
│   │   └── task.controller.ts
│   ├── index.ts
│   ├── models
│   │   ├── schema.ts
│   │   └── types.ts
│   ├── services
│   │   ├── assignee.service.ts
│   │   ├── custom-field.service.ts
│   │   ├── docs.service.ts
│   │   ├── folder.service.ts
│   │   ├── list.service.ts
│   │   ├── space.service.ts
│   │   └── task.service.ts
│   └── utils
│       ├── defineTool.ts
│       └── errors.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
.env
node_modules/
dist/
.vscode/
.idea/
notes.txt

```

--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------

```markdown
# ClickUp MCP Integration

A Model Context Protocol server that provides seamless integration with ClickUp, allowing Large Language Models to interact with your ClickUp workspace tasks and data.

## Available Tools

This MCP server provides the following tools for interacting with ClickUp:

### Task Management

- **`clickup_create_task`**: Creates a new task in your ClickUp workspace
- **`clickup_get_task`**: Retrieves detailed information about a specific task using its ID
- **`clickup_get_task_by_custom_id`**: Retrieves task information using a custom ID
- **`clickup_update_task`**: Updates an existing task by its ID
- **`clickup_update_task_by_custom_id`**: Updates an existing task by its custom ID
- **`get_list_tasks`**: Gets all tasks from a list with optional filtering

### Document Management

- **`clickup_search_docs`**: Searches for docs in a specific parent
- **`clickup_create_doc`**: Creates a new doc in ClickUp
- **`clickup_get_doc_pages`**: Gets all pages from a ClickUp doc
- **`clickup_get_page`**: Gets a specific page from a ClickUp doc
- **`clickup_create_page`**: Creates a new page in a ClickUp doc
- **`clickup_edit_page`**: Edits an existing page in a ClickUp doc

### Custom Fields

- **`clickup_get_list_custom_fields`**: Gets all accessible custom fields for a list
- **`clickup_set_custom_field_value`**: Sets a value for a custom field on a task
- **`clickup_set_custom_field_value_by_custom_id`**: Sets a custom field value using the task's custom ID

### Assignees

- **`get_list_assignees`**: Gets all members (potential assignees) of a list

### Workspace Structure

- **`get_spaces`**: Gets all spaces in the workspace
- **`get_folders`**: Gets all folders in a space
- **`get_lists`**: Gets all lists in a folder
- **`create_list`**: Creates a new list in a folder

## Build

Run:

```bash
npm i
npm run build
npm run inspector
```

Docker build:

```bash
docker buildx build -t {your-docker-repository} --platform linux/amd64,linux/arm64 .
docker push {your-docker-repository}
```

## Setup

**1. Obtaining your ClickUp API Token:**

1. Log in to your ClickUp account at [app.clickup.com](https://app.clickup.com)
2. Navigate to your user settings by clicking your profile picture in the bottom-left corner
3. Select "Settings"
4. Click on "Apps" in the left sidebar
5. Under "API Token", click "Generate" if you don't already have a token
6. Copy the generated API token for use in the MCP server configuration

**2. Finding your Workspace ID:**

1. Open ClickUp in your web browser
2. Look at the URL when you're in your workspace
3. The Workspace ID is the numeric value in the URL: `https://app.clickup.com/{workspace_id}/home`
4. Copy this number for use in the MCP server configuration

**3. Install Docker:** https://docs.docker.com/engine/install/

**4a. Setup Cline MCP Server:**

- Open VSCode or Jetbrains IDEs and go to Cline.
- Go to MCP Servers → Installed → Configure MCP Servers.
- Add the following to your `cline_mcp_settings.json` inside the `mcpServers` key:

```json
"clickup": {
  "command": "docker",
  "args": [
    "run",
    "-i",
    "--rm",
    "-e",
    "CLICKUP_API_TOKEN",
    "-e",
    "CLICKUP_WORKSPACE_ID",
    "your-docker-repository"
  ],
  "env": {
    "CLICKUP_API_TOKEN": "your-api-token",
    "CLICKUP_WORKSPACE_ID": "your-workspace-id"
  }
}
```

**4b. Setup Claude Desktop MCP Server:**

- Use any editor to open the configuration file of Claude Desktop.
  - Windows: `C:\Users\YourUsername\AppData\Roaming\Claude\claude_desktop_config.json`
  - Mac: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
- Add the following to your `claude_desktop_config.json` inside the `mcpServers` key:

```json
"clickup": {
  "command": "docker",
  "args": [
    "run",
    "-i",
    "--rm",
    "-e",
    "CLICKUP_API_TOKEN",
    "-e",
    "CLICKUP_WORKSPACE_ID",
    "your-docker-repository"
  ],
  "env": {
    "CLICKUP_API_TOKEN": "your-api-token",
    "CLICKUP_WORKSPACE_ID": "your-workspace-id"
  }
}
```

- Save the configuration file
- Restart Claude Desktop to apply the changes

### Troubleshooting

If you encounter issues with the MCP server:

1. **Authentication Errors**:

   - Verify your API token is correct
   - Ensure the API token has the necessary permissions for the operations you're attempting
   - Check that your workspace ID is correct

2. **Task Access Issues**:

   - Confirm you have access to the tasks you're trying to retrieve
   - Verify the task IDs are correct and exist in your workspace
   - Check if the tasks might be in an archived state

3. **Connection Problems**:

   - Ensure your Docker service is running properly
   - Check your network connection
   - Verify the environment variables are correctly set in your MCP configuration

## License

This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License.

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM node:22.12-alpine AS builder

COPY . /app
WORKDIR /app

# Install dependencies and build TypeScript code
RUN npm ci && npm run build

# Set environment variables
ENV NODE_ENV=production
# ENV CLICKUP_WORKSPACE_ID=123456789

# Run the server
CMD ["node", "dist/index.js"]

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "./dist",
    "declaration": true,
    "declarationDir": "./dist",
    "sourceMap": true,
    "rootDir": "src"
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

```

--------------------------------------------------------------------------------
/src/controllers/space.controller.ts:
--------------------------------------------------------------------------------

```typescript
import dottenv from "dotenv";
import { defineTool } from "../utils/defineTool";
import SpaceService from "../services/space.service";

dottenv.config();
const apiToken = process.env.CLICKUP_API_TOKEN;
const workspaceId = process.env.CLICKUP_WORKSPACE_ID;

if (!apiToken || !workspaceId) {
  console.error(
    "Please set CLICKUP_API_TOKEN and CLICKUP_WORKSPACE_ID environment variables"
  );
  process.exit(1);
}

const spaceService = new SpaceService(apiToken, workspaceId);

const getSpacesTool = defineTool((z) => ({
  name: "get_spaces",
  description: "Get all spaces in the workspace",
  inputSchema: {},
  handler: async (input) => {
    const response = await spaceService.getSpaces();
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

export { getSpacesTool };

```

--------------------------------------------------------------------------------
/src/services/space.service.ts:
--------------------------------------------------------------------------------

```typescript
import { ClickUpUser } from "../models/types";

const BASE_URL = "https://api.clickup.com/api/v2";

export class SpaceService {
  private readonly headers: { Authorization: string; "Content-Type": string };
  private readonly workspaceId: string;

  constructor(apiToken: string, workspaceId: string) {
    this.workspaceId = workspaceId;
    this.headers = {
      Authorization: apiToken,
      "Content-Type": "application/json",
    };
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const response = await fetch(`${BASE_URL}${endpoint}`, {
      ...options,
      headers: this.headers,
    });
    return response.json();
  }

  async getSpaces() {
    return this.request<{ spaces: any[] }>(`/team/${this.workspaceId}/space`);
  }
}

export default SpaceService;

```

--------------------------------------------------------------------------------
/src/controllers/folder.controller.ts:
--------------------------------------------------------------------------------

```typescript
import dottenv from "dotenv";
import { defineTool } from "../utils/defineTool";
import FolderService from "../services/folder.service";

dottenv.config();
const apiToken = process.env.CLICKUP_API_TOKEN;
const workspaceId = process.env.CLICKUP_WORKSPACE_ID;

if (!apiToken || !workspaceId) {
  console.error(
    "Please set CLICKUP_API_TOKEN and CLICKUP_WORKSPACE_ID environment variables"
  );
  process.exit(1);
}

const folderService = new FolderService(apiToken, workspaceId);

const getFoldersTool = defineTool((z) => ({
  name: "get_folders",
  description: "Get all folders in a space",
  inputSchema: {
    space_id: z.string().describe("ClickUp space ID"),
  },
  handler: async (input) => {
    const { space_id } = input;
    const response = await folderService.getFolders(space_id);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

export { getFoldersTool };

```

--------------------------------------------------------------------------------
/src/services/assignee.service.ts:
--------------------------------------------------------------------------------

```typescript
import { ClickUpUser } from "../models/types";

const BASE_URL = "https://api.clickup.com/api/v2";

export class AssigneeService {
  private readonly headers: { Authorization: string; "Content-Type": string };
  private readonly workspaceId: string;

  constructor(apiToken: string, workspaceId: string) {
    this.workspaceId = workspaceId;
    this.headers = {
      Authorization: apiToken,
      "Content-Type": "application/json",
    };
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const response = await fetch(`${BASE_URL}${endpoint}`, {
      ...options,
      headers: this.headers,
    });
    return response.json();
  }

  async getListMembers(listId: string) {
    // Using the endpoint from https://developer.clickup.com/reference/getlistmembers
    return this.request<{ members: ClickUpUser[] }>(`/list/${listId}/member`);
  }
}

export default AssigneeService;

```

--------------------------------------------------------------------------------
/src/controllers/assignee.controller.ts:
--------------------------------------------------------------------------------

```typescript
import dottenv from "dotenv";
import { defineTool } from "../utils/defineTool";
import AssigneeService from "../services/assignee.service";

dottenv.config();
const apiToken = process.env.CLICKUP_API_TOKEN;
const workspaceId = process.env.CLICKUP_WORKSPACE_ID;

if (!apiToken || !workspaceId) {
  console.error(
    "Please set CLICKUP_API_TOKEN and CLICKUP_WORKSPACE_ID environment variables"
  );
  process.exit(1);
}

const assigneeService = new AssigneeService(apiToken, workspaceId);

const getListAssigneesTool = defineTool((z) => ({
  name: "get_list_assignees",
  description: "Get all members (potential assignees) of a list",
  inputSchema: {
    list_id: z.string().describe("ClickUp list ID"),
  },
  handler: async (input) => {
    const { list_id } = input;
    const response = await assigneeService.getListMembers(list_id);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

export { getListAssigneesTool };

```

--------------------------------------------------------------------------------
/src/services/list.service.ts:
--------------------------------------------------------------------------------

```typescript
import { ClickUpUser } from "../models/types";

const BASE_URL = "https://api.clickup.com/api/v2";

export class ListService {
  private readonly headers: { Authorization: string; "Content-Type": string };
  private readonly workspaceId: string;

  constructor(apiToken: string, workspaceId: string) {
    this.workspaceId = workspaceId;
    this.headers = {
      Authorization: apiToken,
      "Content-Type": "application/json",
    };
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const response = await fetch(`${BASE_URL}${endpoint}`, {
      ...options,
      headers: this.headers,
    });
    return response.json();
  }

  async getLists(folderId: string) {
    return this.request<{ lists: any[] }>(`/folder/${folderId}/list`);
  }

  async createList(
    folderId: string,
    params: {
      name: string;
    }
  ) {
    return this.request(`/folder/${folderId}/list`, {
      method: "POST",
      body: JSON.stringify(params),
    });
  }
}

export default ListService;

```

--------------------------------------------------------------------------------
/.github/workflows/pr_agent.yml:
--------------------------------------------------------------------------------

```yaml
name: PR Agent

on:
  pull_request:
    types: [opened, reopened, ready_for_review]
  issue_comment:

jobs:
  pr_agent_job:
    if: ${{ github.event.sender.type != 'Bot' }}
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
      contents: write
    name: Run pr agent on every pull request, respond to user comments
    steps:
      - name: PR Agent action step
        id: pragent
        uses: docker://codiumai/pr-agent:0.30-github_action
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENAI_KEY: ${{ secrets.PR_AGENT_OPENAI_KEY }}
          # Custom review instructions
          pr_reviewer.extra_instructions: "Review security vulnerabilities and performance issues. Check for proper error handling. Check for spelling errors in user-facing text, API names, or documentation"
          config.ai_timeout: "300"
          # Tool configuration
          github_action_config.auto_review: "true"
          github_action_config.auto_describe: "true"
          github_action_config.auto_improve: "true"

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "clickup-mcp-server",
  "description": "Model Context Protocol server for the ClickUp API",
  "version": "1.0.0",
  "author": "Leanware-io",
  "license": "MIT",
  "type": "module",
  "bin": "dist/index.js",
  "files": [
    "dist"
  ],
  "keywords": [
    "mcp",
    "clickup",
    "cline",
    "claude",
    "model context protocol"
  ],
  "scripts": {
    "build": "tsup src/index.ts --format esm,cjs --dts",
    "start": "node dist/index.js",
    "inspector": "npx @modelcontextprotocol/inspector dist/index.js",
    "clean": "npx rimraf dist"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/Leanware-io/clickup-mcp-server.git"
  },
  "bugs": {
    "url": "https://github.com/Leanware-io/clickup-mcp-server/issues"
  },
  "homepage": "https://github.com/Leanware-io/clickup-mcp-server#readme",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.5.0",
    "dotenv": "^16.4.7",
    "tsup": "^8.3.6",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/node": "^22.13.4",
    "shx": "^0.3.4",
    "typescript": "^5.6.2"
  }
}

```

--------------------------------------------------------------------------------
/src/services/folder.service.ts:
--------------------------------------------------------------------------------

```typescript
import { ClickUpUser } from "../models/types";

const BASE_URL = "https://api.clickup.com/api/v2";

export class FolderService {
  private readonly headers: { Authorization: string; "Content-Type": string };
  private readonly workspaceId: string;

  constructor(apiToken: string, workspaceId: string) {
    this.workspaceId = workspaceId;
    this.headers = {
      Authorization: apiToken,
      "Content-Type": "application/json",
    };
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const response = await fetch(`${BASE_URL}${endpoint}`, {
      ...options,
      headers: this.headers,
    });
    return response.json();
  }

  async getFolders(spaceId: string) {
    const response = await this.request<{ folders: any[] }>(
      `/space/${spaceId}/folder`
    );

    // Remove the "lists" attribute from each folder to reduce payload size
    if (response.folders && Array.isArray(response.folders)) {
      response.folders = response.folders.map((folder) => {
        const { lists, ...folderWithoutLists } = folder;
        return folderWithoutLists;
      });
    }

    return response;
  }
}

export default FolderService;

```

--------------------------------------------------------------------------------
/src/utils/errors.ts:
--------------------------------------------------------------------------------

```typescript
export class ToolError extends Error {
  constructor(
    message: string,
    public code: string,
    public details?: Record<string, any>
  ) {
    super(message);
    this.name = "ToolError";
  }
}

export function formatErrorResponse(error: unknown) {
  if (error instanceof ToolError) {
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify({
            error: {
              code: error.code,
              message: error.message,
              details: error.details,
            },
          }),
        },
      ],
    };
  }

  if (error instanceof Error) {
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify({
            error: {
              code: "INTERNAL_ERROR",
              message: error.message,
              stack:
                process.env.NODE_ENV === "development"
                  ? error.stack
                  : undefined,
            },
          }),
        },
      ],
    };
  }

  return {
    content: [
      {
        type: "text",
        text: JSON.stringify({
          error: {
            code: "UNKNOWN_ERROR",
            message: String(error),
          },
        }),
      },
    ],
  };
}

```

--------------------------------------------------------------------------------
/src/controllers/list.controller.ts:
--------------------------------------------------------------------------------

```typescript
import dottenv from "dotenv";
import { defineTool } from "../utils/defineTool";
import ListService from "../services/list.service";

dottenv.config();
const apiToken = process.env.CLICKUP_API_TOKEN;
const workspaceId = process.env.CLICKUP_WORKSPACE_ID;

if (!apiToken || !workspaceId) {
  console.error(
    "Please set CLICKUP_API_TOKEN and CLICKUP_WORKSPACE_ID environment variables"
  );
  process.exit(1);
}

const listService = new ListService(apiToken, workspaceId);

const getListsTool = defineTool((z) => ({
  name: "get_lists",
  description: "Get all lists in a folder",
  inputSchema: {
    folder_id: z.string().describe("ClickUp folder ID"),
  },
  handler: async (input) => {
    const { folder_id } = input;
    const response = await listService.getLists(folder_id);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const createListTool = defineTool((z) => ({
  name: "create_list",
  description: "Create a new list in a folder",
  inputSchema: {
    folder_id: z.string().describe("ClickUp folder ID"),
    name: z.string().describe("List name"),
  },
  handler: async (input) => {
    const { folder_id, name } = input;
    const response = await listService.createList(folder_id, {
      name,
    });
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

export { getListsTool, createListTool };

```

--------------------------------------------------------------------------------
/src/utils/defineTool.ts:
--------------------------------------------------------------------------------

```typescript
import { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";

export type InferToolHandlerInput<TInputSchema extends z.ZodRawShape> =
  z.objectOutputType<TInputSchema, z.ZodTypeAny>;

type ToolDefinition<TInputSchema extends z.ZodRawShape> = {
  name: string;
  description: string;
  inputSchema: TInputSchema;
  handler: (
    input: InferToolHandlerInput<TInputSchema>
  ) => Promise<Record<string, unknown>>;
};

export const defineTool = <TInputSchema extends z.ZodRawShape>(
  cb: (zod: typeof z) => ToolDefinition<TInputSchema>
) => {
  const tool = cb(z);

  const wrappedHandler = async (
    input: InferToolHandlerInput<TInputSchema>,
    _: RequestHandlerExtra
  ): Promise<CallToolResult> => {
    try {
      const result = await tool.handler(input);
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error: ${
              error instanceof Error ? error.message : String(error)
            }`,
          },
        ],
        isError: true,
      };
    }
  };

  return {
    ...tool,
    handler: wrappedHandler as ToolCallback<TInputSchema>,
  };
};

```

--------------------------------------------------------------------------------
/src/services/custom-field.service.ts:
--------------------------------------------------------------------------------

```typescript
import { ClickUpCustomField } from "../models/types";

const BASE_URL = "https://api.clickup.com/api/v2";

export class CustomFieldService {
  private readonly headers: { Authorization: string; "Content-Type": string };
  private readonly workspaceId: string;

  constructor(apiToken: string, workspaceId: string) {
    this.workspaceId = workspaceId;
    this.headers = {
      Authorization: apiToken,
      "Content-Type": "application/json",
    };
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const response = await fetch(`${BASE_URL}${endpoint}`, {
      ...options,
      headers: this.headers,
    });
    return response.json();
  }

  async getListCustomFields(
    listId: string
  ): Promise<{ fields: ClickUpCustomField[] }> {
    return this.request<{ fields: ClickUpCustomField[] }>(
      `/list/${listId}/field`
    );
  }

  async setCustomFieldValue(
    taskId: string,
    customFieldId: string,
    value: any
  ): Promise<{ field: ClickUpCustomField }> {
    return this.request<{ field: ClickUpCustomField }>(
      `/task/${taskId}/field/${customFieldId}`,
      {
        method: "POST",
        body: JSON.stringify({ value }),
      }
    );
  }

  async setCustomFieldValueByCustomId(
    customId: string,
    customFieldId: string,
    value: any
  ): Promise<{ field: ClickUpCustomField }> {
    return this.request<{ field: ClickUpCustomField }>(
      `/task/${customId}/field/${customFieldId}?custom_task_ids=true&team_id=${this.workspaceId}`,
      {
        method: "POST",
        body: JSON.stringify({ value }),
      }
    );
  }
}

export default CustomFieldService;

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  getTaskByCustomIdTool,
  getTaskTool,
  createTaskTool,
  updateTaskTool,
  updateTaskByCustomIdTool,
} from "./controllers/task.controller";
import {
  searchDocsTool,
  createDocTool,
  getDocPagesTool,
  getPageTool,
  createPageTool,
  editPageTool,
} from "./controllers/docs.controller";
import { getSpacesTool } from "./controllers/space.controller";
import { getFoldersTool } from "./controllers/folder.controller";
import { getListsTool, createListTool } from "./controllers/list.controller";
import {
  getListCustomFieldsTool,
  setCustomFieldValueTool,
  setCustomFieldValueByCustomIdTool,
} from "./controllers/custom-field.controller";
import { getListAssigneesTool } from "./controllers/assignee.controller";

const tools = [
  // Task tools
  getTaskByCustomIdTool,
  getTaskTool,
  createTaskTool,
  updateTaskTool,
  updateTaskByCustomIdTool,

  // Space tools
  getSpacesTool,

  // Folder tools
  getFoldersTool,

  // List tools
  getListsTool,
  createListTool,

  // Custom Field tools
  getListCustomFieldsTool,
  setCustomFieldValueTool,
  setCustomFieldValueByCustomIdTool,

  // Assignee tools
  getListAssigneesTool,

  // Docs tools
  searchDocsTool,
  createDocTool,
  getDocPagesTool,
  getPageTool,
  createPageTool,
  editPageTool,
];

async function main() {
  console.error("Starting ClickUp MCP Server...");

  const apiToken = process.env.CLICKUP_API_TOKEN;
  const workspaceId = process.env.CLICKUP_WORKSPACE_ID;

  if (!apiToken || !workspaceId) {
    console.error(
      "Please set CLICKUP_API_TOKEN and CLICKUP_WORKSPACE_ID environment variables"
    );
    process.exit(1);
  }

  const server = new McpServer(
    {
      name: "ClickUp MCP Server",
      version: "1.0.0",
    },
    {
      capabilities: {
        tools: {},
      },
    }
  );

  tools.forEach((tool) => {
    server.tool(tool.name, tool.description, tool.inputSchema, tool.handler);
  });

  const transport = new StdioServerTransport();
  console.error("Connecting server to transport...");
  await server.connect(transport);

  console.error("ClickUp MCP Server running on stdio");
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

```

--------------------------------------------------------------------------------
/src/models/types.ts:
--------------------------------------------------------------------------------

```typescript
import z from "zod";
import {
  ClickUpTaskSchema,
  ClickUpUserSchema,
  ClickUpCustomFieldSchema,
  ClickUpDocSchema,
  ClickUpDocPageSchema,
} from "./schema";

export type ClickUpUser = z.infer<typeof ClickUpUserSchema>;
export type ClickUpTask = z.infer<typeof ClickUpTaskSchema>;
export type ClickUpCustomField = z.infer<typeof ClickUpCustomFieldSchema>;
export type ClickUpDoc = z.infer<typeof ClickUpDocSchema>;
export type ClickUpDocPage = z.infer<typeof ClickUpDocPageSchema>;

export interface GetListTasksParams {
  archived?: boolean;
  page?: number;
  subtasks?: boolean;
  include_closed?: boolean;
}

export interface CreateTaskParams {
  name: string;
  description?: string;
  markdown_description?: string; // Task description in markdown format
  list_id: string;
  priority?: number; // 1 (Urgent), 2 (High), 3 (Normal), 4 (Low)
  due_date?: number; // Unix timestamp in milliseconds
  tags?: string[]; // Array of tag names
  time_estimate?: number; // Time estimate in milliseconds
  assignees?: number[]; // Array of user IDs to assign to the task
  custom_fields?: Array<{
    id: string;
    value: string | number | boolean | any[] | Record<string, any>;
  }>; // Custom fields to set on task creation
  parent?: string; // Parent task ID to create this task as a subtask
}

export interface UpdateTaskParams {
  name?: string;
  description?: string;
  markdown_description?: string; // Task description in markdown format
  priority?: number; // 1 (Urgent), 2 (High), 3 (Normal), 4 (Low)
  due_date?: number; // Unix timestamp in milliseconds
  tags?: string[]; // Array of tag names
  time_estimate?: number; // Time estimate in milliseconds
  assignees?: {
    add?: number[]; // Array of user IDs to add to the task
    rem?: number[]; // Array of user IDs to remove from the task
  };
  parent?: string; // Parent task ID to move this task as a subtask
}

export interface SearchDocsParams {
  parent_type: string; // Type of parent (e.g., "workspace", "folder", "list")
  parent_id: string; // ID of the parent
}

export interface CreateDocParams {
  name: string;
  parent: {
    id: string;
    type: number; // 4 for Space, 5 for Folder, 6 for List, 7 for Everything, 12 for Workspace
  };
  visibility?: string; // "PRIVATE" by default
  create_page?: boolean; // false by default
}

export interface CreatePageParams {
  docId: string;
  name: string;
  parent_page_id?: string;
  sub_title?: string;
  content: string;
}

export interface EditPageParams {
  docId: string;
  pageId: string;
  name?: string;
  sub_title?: string;
  content?: string;
  content_edit_mode?: string;
}

```

--------------------------------------------------------------------------------
/src/services/task.service.ts:
--------------------------------------------------------------------------------

```typescript
import {
  ClickUpTask,
  ClickUpUser,
  CreateTaskParams,
  UpdateTaskParams,
  GetListTasksParams,
} from "../models/types";

const BASE_URL = "https://api.clickup.com/api/v2";

export class TaskService {
  private readonly headers: { Authorization: string; "Content-Type": string };
  private readonly workspaceId: string;

  constructor(apiToken: string, workspaceId: string) {
    this.workspaceId = workspaceId;
    this.headers = {
      Authorization: apiToken,
      "Content-Type": "application/json",
    };
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const response = await fetch(`${BASE_URL}${endpoint}`, {
      ...options,
      headers: this.headers,
    });
    return response.json();
  }

  async getTask(taskId: string): Promise<ClickUpTask> {
    return this.request<ClickUpTask>(
      `/task/${taskId}?custom_task_ids=false&team_id=${this.workspaceId}&include_subtasks=true&include_markdown_description=true`
    );
  }

  async getTaskByCustomId(customId: string): Promise<ClickUpTask> {
    return this.request<ClickUpTask>(
      `/task/${customId}?custom_task_ids=true&team_id=${this.workspaceId}&include_subtasks=true&include_markdown_description=true`
    );
  }

  async createTask(params: CreateTaskParams): Promise<ClickUpTask> {
    const { list_id, ...taskData } = params;

    return this.request<ClickUpTask>(`/list/${list_id}/task`, {
      method: "POST",
      body: JSON.stringify(taskData),
    });
  }

  async updateTask(
    taskId: string,
    params: UpdateTaskParams
  ): Promise<ClickUpTask> {
    return this.request<ClickUpTask>(`/task/${taskId}`, {
      method: "PUT",
      body: JSON.stringify(params),
    });
  }

  async updateTaskByCustomId(
    customId: string,
    params: UpdateTaskParams
  ): Promise<ClickUpTask> {
    return this.request<ClickUpTask>(
      `/task/${customId}?custom_task_ids=true&team_id=${this.workspaceId}`,
      {
        method: "PUT",
        body: JSON.stringify(params),
      }
    );
  }

  async getListTasks(listId: string, params: GetListTasksParams = {}) {
    const queryParams = new URLSearchParams();

    // Add optional query parameters if they exist
    if (params.archived !== undefined)
      queryParams.append("archived", params.archived.toString());
    if (params.page !== undefined)
      queryParams.append("page", params.page.toString());
    if (params.subtasks !== undefined)
      queryParams.append("subtasks", params.subtasks.toString());
    if (params.include_closed !== undefined)
      queryParams.append("include_closed", params.include_closed.toString());

    const queryString = queryParams.toString()
      ? `?${queryParams.toString()}`
      : "";

    return this.request<{ tasks: ClickUpTask[] }>(
      `/list/${listId}/task${queryString}`
    );
  }
}

export default TaskService;

```

--------------------------------------------------------------------------------
/src/controllers/custom-field.controller.ts:
--------------------------------------------------------------------------------

```typescript
import dottenv from "dotenv";
import { defineTool } from "../utils/defineTool";
import CustomFieldService from "../services/custom-field.service";

dottenv.config();
const apiToken = process.env.CLICKUP_API_TOKEN;
const workspaceId = process.env.CLICKUP_WORKSPACE_ID;

if (!apiToken || !workspaceId) {
  console.error(
    "Please set CLICKUP_API_TOKEN and CLICKUP_WORKSPACE_ID environment variables"
  );
  process.exit(1);
}

const customFieldService = new CustomFieldService(apiToken, workspaceId);

const getListCustomFieldsTool = defineTool((z) => ({
  name: "clickup_get_list_custom_fields",
  description: "Get all accessible custom fields for a list",
  inputSchema: {
    list_id: z.string().describe("ClickUp list ID"),
  },
  handler: async (input) => {
    const { list_id } = input;
    const response = await customFieldService.getListCustomFields(list_id);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const setCustomFieldValueTool = defineTool((z) => ({
  name: "clickup_set_custom_field_value",
  description: "Set a value for a custom field on a task",
  inputSchema: {
    task_id: z.string().describe("ClickUp task ID"),
    custom_field_id: z.string().describe("Custom field ID"),
    value: z
      .union([
        z.string(),
        z.number(),
        z.boolean(),
        z.array(z.unknown()),
        z.record(z.unknown()),
      ])
      .describe(
        "Value to set for the custom field. Type depends on the custom field type."
      ),
  },
  handler: async (input) => {
    const { task_id, custom_field_id, value } = input;
    const response = await customFieldService.setCustomFieldValue(
      task_id,
      custom_field_id,
      value
    );
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const setCustomFieldValueByCustomIdTool = defineTool((z) => ({
  name: "clickup_set_custom_field_value_by_custom_id",
  description:
    "Set a value for a custom field on a task using the task's custom ID",
  inputSchema: {
    custom_id: z.string().describe("ClickUp custom task ID"),
    custom_field_id: z.string().describe("Custom field ID"),
    value: z
      .union([
        z.string(),
        z.number(),
        z.boolean(),
        z.array(z.unknown()),
        z.record(z.unknown()),
      ])
      .describe(
        "Value to set for the custom field. Type depends on the custom field type."
      ),
  },
  handler: async (input) => {
    const { custom_id, custom_field_id, value } = input;
    const response = await customFieldService.setCustomFieldValueByCustomId(
      custom_id,
      custom_field_id,
      value
    );
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

export {
  getListCustomFieldsTool,
  setCustomFieldValueTool,
  setCustomFieldValueByCustomIdTool,
};

```

--------------------------------------------------------------------------------
/src/services/docs.service.ts:
--------------------------------------------------------------------------------

```typescript
import {
  ClickUpDoc,
  ClickUpDocPage,
  CreateDocParams,
  CreatePageParams,
  EditPageParams,
  SearchDocsParams,
} from "../models/types";

const BASE_URL = "https://api.clickup.com/api/v3/workspaces";

export class DocsService {
  private readonly headers: { Authorization: string; "Content-Type": string };
  private readonly workspaceId: string;

  constructor(apiToken: string, workspaceId: string) {
    this.workspaceId = workspaceId;
    this.headers = {
      Authorization: apiToken,
      "Content-Type": "application/json",
    };
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const response = await fetch(`${BASE_URL}${endpoint}`, {
      ...options,
      headers: this.headers,
    });
    return response.json();
  }

  async searchDocs(params: SearchDocsParams): Promise<{ docs: ClickUpDoc[] }> {
    const { parent_type, parent_id } = params;
    return this.request<{ docs: ClickUpDoc[] }>(
      `/${this.workspaceId}/docs?parent_type=${parent_type}&parent_id=${parent_id}`
    );
  }

  async createDoc(params: CreateDocParams): Promise<ClickUpDoc> {
    const docData = {
      name: params.name,
      parent: params.parent,
      visibility: params.visibility || "PRIVATE",
      create_page:
        params.create_page !== undefined ? params.create_page : false,
    };

    return this.request<ClickUpDoc>(`/${this.workspaceId}/docs`, {
      method: "POST",
      body: JSON.stringify(docData),
    });
  }

  async getDocPages(docId: string): Promise<{ pages: ClickUpDocPage[] }> {
    return this.request<{ pages: ClickUpDocPage[] }>(
      `/${this.workspaceId}/docs/${docId}/pageListing`
    );
  }

  async getPage(docId: string, pageId: string): Promise<ClickUpDocPage> {
    return this.request<ClickUpDocPage>(
      `/${this.workspaceId}/docs/${docId}/pages/${pageId}`
    );
  }

  async createPage(params: CreatePageParams): Promise<ClickUpDocPage> {
    const { docId, name, parent_page_id, sub_title, content } = params;
    const pageData = {
      name,
      parent_page_id: parent_page_id || null,
      sub_title: sub_title || null,
      content,
      content_format: "text/md",
    };

    return this.request<ClickUpDocPage>(
      `/${this.workspaceId}/docs/${docId}/pages`,
      {
        method: "POST",
        body: JSON.stringify(pageData),
      }
    );
  }

  async editPage(params: EditPageParams): Promise<ClickUpDocPage> {
    const { docId, pageId, name, sub_title, content, content_edit_mode } =
      params;
    const pageData = {
      name,
      sub_title,
      content,
      content_edit_mode: content_edit_mode || "replace",
      content_format: "text/md",
    };

    return this.request<ClickUpDocPage>(
      `/${this.workspaceId}/docs/${docId}/pages/${pageId}`,
      {
        method: "PUT",
        body: JSON.stringify(pageData),
      }
    );
  }
}

export default DocsService;

```

--------------------------------------------------------------------------------
/src/models/schema.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";

export const ClickUpDocSchema = z.object({
  id: z.string(),
  title: z.string(),
  date_created: z.string(),
  date_updated: z.string(),
  folder: z
    .object({
      id: z.string(),
      name: z.string(),
      hidden: z.boolean(),
      access: z.boolean(),
    })
    .optional(),
  list: z
    .object({
      id: z.string(),
      name: z.string(),
      access: z.boolean(),
    })
    .optional(),
  project: z
    .object({
      id: z.string(),
      name: z.string(),
      hidden: z.boolean(),
      access: z.boolean(),
    })
    .optional(),
  space: z.object({
    id: z.string(),
  }),
  user: z.object({
    id: z.number(),
    username: z.string(),
    email: z.string(),
    color: z.string(),
  }),
  shared: z.boolean(),
  members: z.array(
    z.object({
      user: z.object({
        id: z.number(),
        username: z.string(),
        email: z.string(),
        color: z.string(),
      }),
      permission_level: z.string(),
    })
  ),
  permission_level: z.string(),
  parent: z.object({
    id: z.string(),
    type: z.number(),
  }),
});

export const ClickUpDocPageSchema = z.object({
  id: z.string(),
  title: z.string(),
  date_created: z.string(),
  date_updated: z.string(),
  parent: z.string().nullable(),
  content: z.string().optional(),
  children: z.array(z.string()).optional(),
});

export const ClickUpCustomFieldSchema = z.object({
  id: z.string(),
  name: z.string(),
  type: z.string(),
  type_config: z.unknown(),
  date_created: z.string(),
  hide_from_guests: z.boolean(),
  required: z.boolean(),
  value: z
    .union([
      z.string(),
      z.number(),
      z.null(),
      z.array(z.unknown()),
      z.record(z.unknown()),
    ])
    .optional(),
});

export const ClickUpUserSchema = z.object({
  id: z.number(),
  username: z.string(),
  email: z.string(),
  color: z.string(),
  profilePicture: z.string().url(),
  initials: z.string(),
  week_start_day: z.number(),
  global_font_support: z.string().nullable(),
  timezone: z.string(),
});

export const ClickUpTaskSchema = z.object({
  id: z.string(),
  custom_id: z.string().nullable(),
  custom_item_id: z.number().nullable(),
  name: z.string(),
  text_content: z.string(),
  description: z.string(),
  status: z.object({
    status: z.string(),
    color: z.string(),
    orderindex: z.number(),
    type: z.string(),
  }),
  orderindex: z.string(),
  date_created: z.string(),
  date_updated: z.string(),
  date_closed: z.string().nullable(),
  markdown_description: z.string().nullable(),
  date_done: z.string().nullable(),
  archived: z.boolean(),
  creator: z.object({
    id: z.number(),
    username: z.string(),
    color: z.string(),
    email: z.string(),
    profilePicture: z.string().nullable(),
  }),
  assignees: z.array(
    z.object({
      id: z.number(),
      username: z.string(),
      color: z.string().nullable(),
      initials: z.string(),
      email: z.string(),
      profilePicture: z.string().nullable(),
    })
  ),
  watchers: z.array(
    z.object({
      id: z.number(),
      username: z.string(),
      color: z.string(),
      initials: z.string(),
      email: z.string(),
      profilePicture: z.string().nullable(),
    })
  ),
  checklists: z.array(z.unknown()),
  tags: z.array(
    z.object({
      name: z.string(),
      tag_fg: z.string(),
      tag_bg: z.string(),
      creator: z.number(),
    })
  ),
  parent: z.string().nullable(),
  top_level_parent: z.string().nullable(),
  priority: z.object({
    color: z.string(),
    id: z.string(),
    orderindex: z.string(),
    priority: z.string(),
  }),
  due_date: z.string().nullable(),
  start_date: z.string().nullable(),
  points: z.number().nullable(),
  time_estimate: z.number().nullable(),
  time_spent: z.number(),
  custom_fields: z.array(
    z.object({
      id: z.string(),
      name: z.string(),
      type: z.string(),
      type_config: z.unknown(),
      date_created: z.string(),
      hide_from_guests: z.boolean(),
      required: z.boolean(),
      value: z.union([z.string(), z.number(), z.null()]).optional(),
    })
  ),
  dependencies: z.array(z.unknown()),
  linked_tasks: z.array(z.unknown()),
  locations: z.array(z.unknown()),
  team_id: z.string(),
  url: z.string(),
  sharing: z.object({
    public: z.boolean(),
    public_share_expires_on: z.string().nullable(),
    public_fields: z.array(z.string()),
    token: z.string().nullable(),
    seo_optimized: z.boolean(),
  }),
  permission_level: z.string(),
  list: z.object({
    id: z.string(),
    name: z.string(),
    access: z.boolean(),
  }),
  project: z.object({
    id: z.string(),
    name: z.string(),
    hidden: z.boolean(),
    access: z.boolean(),
  }),
  folder: z.object({
    id: z.string(),
    name: z.string(),
    hidden: z.boolean(),
    access: z.boolean(),
  }),
  space: z.object({
    id: z.string(),
  }),
  attachments: z.array(z.unknown()),
});

```

--------------------------------------------------------------------------------
/src/controllers/docs.controller.ts:
--------------------------------------------------------------------------------

```typescript
import dottenv from "dotenv";
import { defineTool } from "../utils/defineTool";
import DocsService from "../services/docs.service";
import {
  SearchDocsParams,
  CreateDocParams,
  CreatePageParams,
  EditPageParams,
} from "../models/types";

dottenv.config();
const apiToken = process.env.CLICKUP_API_TOKEN;
const workspaceId = process.env.CLICKUP_WORKSPACE_ID;

if (!apiToken || !workspaceId) {
  console.error(
    "Please set CLICKUP_API_TOKEN and CLICKUP_WORKSPACE_ID environment variables"
  );
  process.exit(1);
}

const docsService = new DocsService(apiToken, workspaceId);

const searchDocsTool = defineTool((z) => ({
  name: "clickup_search_docs",
  description: "Search for docs in a specific parent",
  inputSchema: {
    parent_type: z
      .string()
      .describe("Type of parent (SPACE, FOLDER, LIST, EVERYTHING, WORKSPACE)"),
    parent_id: z.string().describe("ID of the parent"),
  },
  handler: async (input) => {
    const params: SearchDocsParams = {
      parent_type: input.parent_type,
      parent_id: input.parent_id,
    };
    const response = await docsService.searchDocs(params);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const createDocTool = defineTool((z) => ({
  name: "clickup_create_doc",
  description: "Create a new doc in ClickUp",
  inputSchema: {
    name: z.string().describe("The name of the new Doc"),
    parent: z
      .object({
        id: z.string().describe("Parent ID"),
        type: z
          .number()
          .describe(
            "Parent type: 4 for Space, 5 for Folder, 6 for List, 7 for Everything, 12 for Workspace"
          ),
      })
      .describe("Parent object"),
    visibility: z
      .string()
      .optional()
      .describe("Doc visibility (PUBLIC or PRIVATE), PRIVATE by default"),
    create_page: z
      .boolean()
      .optional()
      .describe("Whether to create a initial page (false by default)"),
  },
  handler: async (input) => {
    const docParams: CreateDocParams = {
      name: input.name,
      parent: input.parent,
      visibility: input.visibility,
      create_page: input.create_page,
    };
    const response = await docsService.createDoc(docParams);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const getDocPagesTool = defineTool((z) => ({
  name: "clickup_get_doc_pages",
  description: "Get pages from a ClickUp doc",
  inputSchema: {
    doc_id: z.string().describe("ClickUp doc ID"),
  },
  handler: async (input) => {
    const response = await docsService.getDocPages(input.doc_id);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const getPageTool = defineTool((z) => ({
  name: "clickup_get_page",
  description: "Get a page from a ClickUp doc",
  inputSchema: {
    doc_id: z.string().describe("ClickUp doc ID"),
    page_id: z.string().describe("ClickUp page ID"),
  },
  handler: async (input) => {
    const response = await docsService.getPage(input.doc_id, input.page_id);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const createPageTool = defineTool((z) => ({
  name: "clickup_create_page",
  description: "Create a new page in a ClickUp doc",
  inputSchema: {
    doc_id: z.string().describe("ClickUp doc ID"),
    name: z.string().describe("Page name"),
    parent_page_id: z
      .string()
      .optional()
      .describe("Parent page ID (null for root page)"),
    sub_title: z.string().optional().describe("Page subtitle"),
    content: z.string().describe("Page content in markdown format"),
  },
  handler: async (input) => {
    const pageParams: CreatePageParams = {
      docId: input.doc_id,
      name: input.name,
      parent_page_id: input.parent_page_id,
      sub_title: input.sub_title,
      content: input.content,
    };
    const response = await docsService.createPage(pageParams);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const editPageTool = defineTool((z) => ({
  name: "clickup_edit_page",
  description: "Edit a page in a ClickUp doc",
  inputSchema: {
    doc_id: z.string().describe("ClickUp doc ID"),
    page_id: z.string().describe("ClickUp page ID"),
    name: z.string().optional().describe("Page name"),
    sub_title: z.string().optional().describe("Page subtitle"),
    content: z.string().optional().describe("Page content in markdown format"),
    content_edit_mode: z
      .string()
      .optional()
      .describe(
        "Content edit mode (replace, append, prepend), default is replace"
      ),
  },
  handler: async (input) => {
    const pageParams: EditPageParams = {
      docId: input.doc_id,
      pageId: input.page_id,
      name: input.name,
      sub_title: input.sub_title,
      content: input.content,
      content_edit_mode: input.content_edit_mode,
    };
    const response = await docsService.editPage(pageParams);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

export {
  searchDocsTool,
  createDocTool,
  getDocPagesTool,
  getPageTool,
  createPageTool,
  editPageTool,
};

```

--------------------------------------------------------------------------------
/src/controllers/task.controller.ts:
--------------------------------------------------------------------------------

```typescript
import dottenv from "dotenv";
import { defineTool } from "../utils/defineTool";
import TaskService from "../services/task.service";
import {
  CreateTaskParams,
  UpdateTaskParams,
  GetListTasksParams,
} from "../models/types";

dottenv.config();
const apiToken = process.env.CLICKUP_API_TOKEN;
const workspaceId = process.env.CLICKUP_WORKSPACE_ID;

if (!apiToken || !workspaceId) {
  console.error(
    "Please set CLICKUP_API_TOKEN and CLICKUP_WORKSPACE_ID environment variables"
  );
  process.exit(1);
}

const taskService = new TaskService(apiToken, workspaceId);

const getTaskTool = defineTool((z) => ({
  name: "clickup_get_task",
  description: "Get a task by its ID",
  inputSchema: {
    task_id: z.string(),
  },
  handler: async (input) => {
    const { task_id } = input;
    const response = await taskService.getTask(task_id);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const getTaskByCustomIdTool = defineTool((z) => ({
  name: "clickup_get_task_by_custom_id",
  description: "Get a task by its custom ID",
  inputSchema: {
    custom_id: z.string(),
  },
  handler: async (input) => {
    const { custom_id } = input;
    const response = await taskService.getTaskByCustomId(custom_id);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const createTaskTool = defineTool((z) => ({
  name: "clickup_create_task",
  description: "Create a new task in ClickUp",
  inputSchema: {
    name: z.string().describe("Task name"),
    markdown_description: z
      .string()
      .optional()
      .describe("Task description in markdown format"),
    list_id: z.string().describe("ClickUp list ID"),
    priority: z
      .number()
      .optional()
      .describe("Task priority (1-4): 1=Urgent, 2=High, 3=Normal, 4=Low"),
    due_date: z
      .number()
      .optional()
      .describe("Due date as Unix timestamp in milliseconds"),
    tags: z
      .array(z.string())
      .optional()
      .describe("Array of tag names to add to the task"),
    time_estimate: z
      .number()
      .optional()
      .describe("Time estimate in milliseconds"),
    assignees: z
      .array(z.number())
      .optional()
      .describe("Array of user IDs to assign to the task"),
    custom_fields: z
      .array(
        z.object({
          id: z.string().describe("Custom field ID"),
          value: z
            .union([
              z.string(),
              z.number(),
              z.boolean(),
              z.array(z.unknown()),
              z.record(z.unknown()),
            ])
            .describe("Value for the custom field"),
        })
      )
      .optional()
      .describe("Custom fields to set on task creation"),
    parent: z
      .string()
      .optional()
      .describe("Parent task ID to create this task as a subtask"),
  },
  handler: async (input): Promise<any> => {
    const taskParams: CreateTaskParams = {
      name: input.name,
      list_id: input.list_id,
      markdown_description: input.markdown_description,
      priority: input.priority,
      due_date: input.due_date,
      tags: input.tags,
      time_estimate: input.time_estimate,
      assignees: input.assignees,
      custom_fields: input.custom_fields,
      parent: input.parent,
    };

    const response = await taskService.createTask(taskParams);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const updateTaskTool = defineTool((z) => ({
  name: "clickup_update_task",
  description: "Update a task by its ID",
  inputSchema: {
    task_id: z.string().describe("ClickUp task ID"),
    name: z.string().optional().describe("Task name"),
    markdown_description: z
      .string()
      .optional()
      .describe("Task description in markdown format"),
    priority: z
      .number()
      .optional()
      .describe("Task priority (1-4): 1=Urgent, 2=High, 3=Normal, 4=Low"),
    due_date: z
      .number()
      .optional()
      .describe("Due date as Unix timestamp in milliseconds"),
    tags: z
      .array(z.string())
      .optional()
      .describe("Array of tag names to add to the task"),
    time_estimate: z
      .number()
      .optional()
      .describe("Time estimate in milliseconds"),
    assignees: z
      .object({
        add: z
          .array(z.number())
          .optional()
          .describe("Array of user IDs to add to the task"),
        rem: z
          .array(z.number())
          .optional()
          .describe("Array of user IDs to remove from the task"),
      })
      .optional()
      .describe("User IDs to add or remove from the task"),
    parent: z
      .string()
      .optional()
      .describe("Parent task ID to move this task as a subtask"),
  },
  handler: async (input): Promise<any> => {
    const { task_id, ...updateData } = input;
    const taskParams: UpdateTaskParams = {
      name: updateData.name,
      markdown_description: updateData.markdown_description,
      priority: updateData.priority,
      due_date: updateData.due_date,
      tags: updateData.tags,
      time_estimate: updateData.time_estimate,
      assignees: updateData.assignees,
      parent: updateData.parent,
    };

    const response = await taskService.updateTask(task_id, taskParams);
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const updateTaskByCustomIdTool = defineTool((z) => ({
  name: "clickup_update_task_by_custom_id",
  description: "Update a task by its custom ID",
  inputSchema: {
    custom_id: z.string().describe("ClickUp custom task ID"),
    name: z.string().optional().describe("Task name"),
    markdown_description: z
      .string()
      .optional()
      .describe("Task description in markdown format"),
    priority: z
      .number()
      .optional()
      .describe("Task priority (1-4): 1=Urgent, 2=High, 3=Normal, 4=Low"),
    due_date: z
      .number()
      .optional()
      .describe("Due date as Unix timestamp in milliseconds"),
    tags: z
      .array(z.string())
      .optional()
      .describe("Array of tag names to add to the task"),
    time_estimate: z
      .number()
      .optional()
      .describe("Time estimate in milliseconds"),
    assignees: z
      .object({
        add: z
          .array(z.number())
          .optional()
          .describe("Array of user IDs to add to the task"),
        rem: z
          .array(z.number())
          .optional()
          .describe("Array of user IDs to remove from the task"),
      })
      .optional()
      .describe("User IDs to add or remove from the task"),
    parent: z
      .string()
      .optional()
      .describe("Parent task ID to move this task as a subtask"),
  },
  handler: async (input): Promise<any> => {
    const { custom_id, ...updateData } = input;
    const taskParams: UpdateTaskParams = {
      name: updateData.name,
      markdown_description: updateData.markdown_description,
      priority: updateData.priority,
      due_date: updateData.due_date,
      tags: updateData.tags,
      time_estimate: updateData.time_estimate,
      assignees: updateData.assignees,
      parent: updateData.parent,
    };

    const response = await taskService.updateTaskByCustomId(
      custom_id,
      taskParams
    );
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

const getListTasksTool = defineTool((z) => ({
  name: "get_list_tasks",
  description: "Get tasks from a ClickUp list with optional filtering",
  inputSchema: {
    list_id: z.string().describe("ClickUp list ID"),
    archived: z.boolean().optional().describe("Include archived tasks"),
    page: z.number().optional().describe("Page number for pagination"),
    subtasks: z.boolean().optional().describe("Include subtasks"),
    include_closed: z.boolean().optional().describe("Include closed tasks"),
  },
  handler: async (input) => {
    const { list_id, ...params } = input;
    const response = await taskService.getListTasks(
      list_id,
      params as GetListTasksParams
    );
    return {
      content: [{ type: "text", text: JSON.stringify(response) }],
    };
  },
}));

export {
  getTaskByCustomIdTool,
  getTaskTool,
  createTaskTool,
  updateTaskTool,
  updateTaskByCustomIdTool,
  getListTasksTool,
};

```