@taazkareem/clickup-mcp-server
Version:
ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol
483 lines (482 loc) • 16.7 kB
JavaScript
/**
* SPDX-FileCopyrightText: © 2025 João Santana <joaosantana@gmail.com>
* SPDX-License-Identifier: MIT
*
* ClickUp MCP Document Tools
*
* This module defines document-related tools including creating,
* retrieving, updating, and deleting documents.
*/
import { workspaceService } from '../services/shared.js';
import config from '../config.js';
import { sponsorService } from '../utils/sponsor-service.js';
import { Logger } from "../logger.js";
import { clickUpServices } from "../services/shared.js";
const logger = new Logger('DocumentTools');
const { document: documentService } = clickUpServices;
/**
* Tool definition for creating a document
*/
export const createDocumentTool = {
name: "create_document",
description: `Creates a document in a ClickUp space, folder, or list. Requires name, parent info, visibility and create_page flag.`,
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name and Title of the document"
},
parent: {
type: "object",
properties: {
id: {
type: "string",
description: "ID of the parent container (space, folder, or list)"
},
type: {
type: "number",
enum: [4, 5, 6, 7, 12],
description: "Type of the parent container (4=space, 5=folder, 6=list, 7=everything, 12=workspace)"
}
},
required: ["id", "type"],
description: "Parent container information"
},
visibility: {
type: "string",
enum: ["PUBLIC", "PRIVATE"],
description: "Document visibility setting"
},
create_page: {
type: "boolean",
description: "Whether to create an initial blank page"
}
},
required: ["name", "parent", "visibility", "create_page"]
}
};
/**
* Tool definition for getting a document
*/
export const getDocumentTool = {
name: "get_document",
description: `Gets details of a ClickUp document. Use documentId (preferred) or search by title in a container.`,
inputSchema: {
type: "object",
properties: {
documentId: {
type: "string",
description: "ID of the document to retrieve"
},
},
required: ["documentId"]
}
};
/**
* Tool definition for listing documents
*/
export const listDocumentsTool = {
name: "list_documents",
description: `Lists all documents in a ClickUp space, folder, or list.`,
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Optional document ID to filter by"
},
creator: {
type: "number",
description: "Optional creator ID to filter by"
},
deleted: {
type: "boolean",
description: "Whether to include deleted documents"
},
archived: {
type: "boolean",
description: "Whether to include archived documents"
},
parent_id: {
type: "string",
description: "ID of the parent container to list documents from"
},
parent_type: {
type: "string",
enum: ["TASK", "SPACE", "FOLDER", "LIST", "EVERYTHING", "WORKSPACE"],
description: "Type of the parent container"
},
limit: {
type: "number",
description: "Maximum number of documents to return"
},
next_cursor: {
type: "string",
description: "Cursor for pagination"
}
},
required: []
}
};
/**
* Tool definition for listing document pages
*/
export const listDocumentPagesTool = {
name: "list_document_pages",
description: "Lists all pages in a document with optional depth control",
inputSchema: {
type: "object",
properties: {
documentId: {
type: "string",
description: "ID of the document to list pages from"
},
max_page_depth: {
type: "number",
description: "Maximum depth of pages to retrieve (-1 for unlimited)"
}
},
required: ["documentId"]
}
};
/**
* Tool definition for getting document pages
*/
export const getDocumentPagesTool = {
name: "get_document_pages",
description: "Gets the content of specific pages from a document",
inputSchema: {
type: "object",
properties: {
documentId: {
type: "string",
description: "ID of the document to get pages from"
},
pageIds: {
type: "array",
items: {
type: "string"
},
description: "Array of page IDs to retrieve"
},
content_format: {
type: "string",
enum: ["text/md", "text/html"],
description: "Format of the content to retrieve"
}
},
required: ["documentId", "pageIds"]
}
};
/**
* Tool definition for creating a document page
*/
export const createDocumentPageTool = {
name: "create_document_page",
description: "Creates a new page in a ClickUp document",
inputSchema: {
type: "object",
properties: {
documentId: {
type: "string",
description: "ID of the document to create the page in"
},
content: {
type: "string",
description: "Content of the page"
},
name: {
type: "string",
description: "Name and title of the page",
},
sub_title: {
type: "string",
description: "Subtitle of the page"
},
parent_page_id: {
type: "string",
description: "ID of the parent page (if this is a sub-page)"
}
},
required: ["documentId", "name"]
}
};
/**
* Tool definition for updating a document page
*/
export const updateDocumentPageTool = {
name: "update_document_page",
description: "Updates an existing page in a ClickUp document. Supports updating name, subtitle, and content with different edit modes (replace/append/prepend).",
inputSchema: {
type: "object",
properties: {
documentId: {
type: "string",
description: "ID of the document containing the page"
},
pageId: {
type: "string",
description: "ID of the page to update"
},
name: {
type: "string",
description: "New name for the page"
},
sub_title: {
type: "string",
description: "New subtitle for the page"
},
content: {
type: "string",
description: "New content for the page"
},
content_edit_mode: {
type: "string",
enum: ["replace", "append", "prepend"],
description: "How to update the content. Defaults to replace"
},
content_format: {
type: "string",
enum: ["text/md", "text/plain"],
description: "Format of the content. Defaults to text/md"
},
},
required: ["documentId", "pageId"]
}
};
/**
* Helper function to find a document by title in a container
*/
async function findDocumentByTitle(parentId, title) {
const response = await documentService.listDocuments({
parent_id: parentId
});
const document = response.docs.find(doc => doc.name === title);
return document ? document.id : null;
}
/**
* Helper function to find parent container ID by name and type
*/
async function findParentIdByName(name, type) {
const hierarchy = await workspaceService.getWorkspaceHierarchy();
const container = workspaceService.findIDByNameInHierarchy(hierarchy, name, type);
return container ? container.id : null;
}
/**
* Handler for the create_document tool
*/
export async function handleCreateDocument(parameters) {
const { name, parent, visibility, create_page } = parameters;
if (!parent || !visibility || !create_page) {
return sponsorService.createErrorResponse('Parent, visibility, and create_page are required');
}
// Prepare document data
const documentData = {
name,
parent,
visibility,
create_page
};
try {
// Create the document
const newDocument = await clickUpServices.document.createDocument(documentData);
return sponsorService.createResponse({
id: newDocument.id,
name: newDocument.name,
parent: newDocument.parent,
url: `https://app.clickup.com/${config.clickupTeamId}/v/d/${newDocument.id}`,
message: `Document "${name}" created successfully`
}, true);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to create document: ${error.message}`);
}
}
/**
* Handler for the get_document tool
*/
export async function handleGetDocument(parameters) {
const { documentId, title, parentId } = parameters;
let targetDocumentId = documentId;
// If no documentId but title and parentId are provided, look up the document ID
if (!targetDocumentId && title && parentId) {
targetDocumentId = await findDocumentByTitle(parentId, title);
if (!targetDocumentId) {
throw new Error(`Document "${title}" not found`);
}
}
if (!targetDocumentId) {
throw new Error("Either documentId or (title + parentId) must be provided");
}
try {
// Get the document
const document = await documentService.getDocument(targetDocumentId);
return sponsorService.createResponse({
id: document.id,
name: document.name,
parent: document.parent,
created: new Date(document.date_created).toISOString(),
updated: new Date(document.date_updated).toISOString(),
creator: document.creator,
public: document.public,
type: document.type,
url: `https://app.clickup.com/${config.clickupTeamId}/v/d/${document.id}`
}, true);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to retrieve document: ${error.message}`);
}
}
/**
* Handler for the list_documents tool
*/
export async function handleListDocuments(parameters) {
const { id, creator, deleted, archived, parent_id, parent_type, limit, next_cursor } = parameters;
try {
// Prepare options object with all possible parameters
const options = {};
// Add each parameter to options only if it's defined
if (id !== undefined)
options.id = id;
if (creator !== undefined)
options.creator = creator;
if (deleted !== undefined)
options.deleted = deleted;
if (archived !== undefined)
options.archived = archived;
if (parent_id !== undefined)
options.parent_id = parent_id;
if (parent_type !== undefined)
options.parent_type = parent_type;
if (limit !== undefined)
options.limit = limit;
if (next_cursor !== undefined)
options.next_cursor = next_cursor;
const response = await documentService.listDocuments(options);
// Ensure we have a valid response
if (!response || !response.docs) {
return sponsorService.createResponse({
documents: [],
message: "No documents found"
}, true);
}
// Map the documents to a simpler format
const documents = response.docs.map(doc => ({
id: doc.id,
name: doc.name,
url: `https://app.clickup.com/${config.clickupTeamId}/v/d/${doc.id}`,
parent: doc.parent,
created: new Date(doc.date_created).toISOString(),
updated: new Date(doc.date_updated).toISOString(),
creator: doc.creator,
public: doc.public,
type: doc.type
}));
return sponsorService.createResponse({
documents,
count: documents.length,
next_cursor: response.next_cursor,
message: `Found ${documents.length} document(s)`
}, true);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to list documents: ${error.message}`);
}
}
/**
* Handler for listing document pages
*/
export async function handleListDocumentPages(params) {
logger.info('Listing document pages', { params });
try {
const { documentId, max_page_depth = -1 } = params;
const pages = await documentService.listDocumentPages(documentId, { max_page_depth });
return sponsorService.createResponse(pages);
}
catch (error) {
logger.error('Error listing document pages', error);
return sponsorService.createErrorResponse(error);
}
}
/**
* Handler for getting document pages
*/
export async function handleGetDocumentPages(params) {
const { documentId, pageIds, content_format } = params;
if (!documentId) {
return sponsorService.createErrorResponse('Document ID is required');
}
if (!pageIds || !Array.isArray(pageIds) || pageIds.length === 0) {
return sponsorService.createErrorResponse('Page IDs array is required');
}
try {
const options = {};
// Adiciona content_format nas options se fornecido
if (content_format) {
options.content_format = content_format;
}
const pages = await clickUpServices.document.getDocumentPages(documentId, pageIds, options);
return sponsorService.createResponse(pages);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to get document pages: ${error.message}`);
}
}
/**
* Handler for creating a new page in a document
*/
export async function handleCreateDocumentPage(parameters) {
const { documentId, content, sub_title, name, parent_page_id } = parameters;
if (!documentId) {
return sponsorService.createErrorResponse('Document ID is required');
}
if (!name) {
return sponsorService.createErrorResponse('Title/Name is required');
}
try {
const page = await clickUpServices.document.createPage(documentId, {
content,
sub_title,
name,
parent_page_id,
});
return sponsorService.createResponse(page);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to create document page: ${error.message}`);
}
}
/**
* Handler for updating a document page
*/
export async function handleUpdateDocumentPage(parameters) {
const { documentId, pageId, name, sub_title, content, content_format, content_edit_mode } = parameters;
if (!documentId) {
return sponsorService.createErrorResponse('Document ID is required');
}
if (!pageId) {
return sponsorService.createErrorResponse('Page ID is required');
}
// Prepare update data
const updateData = {};
if (name)
updateData.name = name;
if (sub_title)
updateData.sub_title = sub_title;
if (content)
updateData.content = content;
if (content_format)
updateData.content_format = content_format;
if (content_edit_mode)
updateData.content_edit_mode = content_edit_mode;
try {
const page = await clickUpServices.document.updatePage(documentId, pageId, updateData);
return sponsorService.createResponse({
message: `Page updated successfully`
}, true);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to update document page: ${error.message}`);
}
}