basecamp_update_todo
Modify Basecamp todo items by updating titles, assigning people, or editing content using partial operations to reduce token usage.
Instructions
Update a todo item. Use partial content operations when possible to save on token usage.
HTML rules for content:
Allowed tags: div, span, h1, br, strong, em, strike, a (with an href attribute), pre, ol, ul, li, blockquote, bc-attachment (with sgid attribute).
Try to be semantic despite the limitations of tags. Use double to create paragraph spacing
To mention people:
To consume less tokens, existing tags can be rewritten by dropping any attributes/inner content and just leave the "sgid" and "caption" attributes, without loosing any information
You can highlight parts of the content in this format ... valid colors are:
red: 255, 229, 229
yellow: 250, 247, 133
green: 228, 248, 226
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| bucket_id | Yes | Basecamp resource identifier | |
| todo_id | Yes | ||
| title | No | New todo title | |
| assignee_ids | No | Array of person IDs to assign to this todo | |
| content | No | If provided, replaces entire HTML content. Cannot be used with content_append, content_prepend, or search_replace. | |
| content_append | No | Text to append to the end of current content. Cannot be used with content. | |
| content_prepend | No | Text to prepend to the beginning of current content. Cannot be used with content. | |
| search_replace | No | Array of search-replace operations to apply to current content. Cannot be used with content. |
Implementation Reference
- src/tools/todos.ts:301-377 (handler)The handler function for 'basecamp_update_todo' tool. It validates inputs, fetches the current todo state, applies content operations for partial updates to description, computes new title and assignees, calls the Basecamp todos.update API, and returns success or error message.async (params) => { try { // Validate at least one operation is provided validateContentOperations(params, ["title", "assignee_ids"]); const client = await initializeBasecampClient(); // Fetch current todo to get existing values const currentResponse = await client.todos.get({ params: { bucketId: params.bucket_id, todoId: params.todo_id, }, }); if (currentResponse.status !== 200 || !currentResponse.body) { throw new Error( `Failed to fetch current todo: ${currentResponse.status}`, ); } // Determine final title (maps to Basecamp's content field) const finalTitle = params.title ?? currentResponse.body.content; // Determine final content (maps to Basecamp's description field) let finalContent: string | undefined; const hasPartialOps = params.content_append || params.content_prepend || params.search_replace; if (hasPartialOps) { const currentContent = currentResponse.body.description || ""; const result = applyContentOperations(currentContent, params); if (result === undefined) { throw new Error("Content operations resulted in undefined content"); } finalContent = result; } else if (params.content !== undefined) { // Full content replacement finalContent = params.content; } const response = await client.todos.update({ params: { bucketId: params.bucket_id, todoId: params.todo_id, }, body: { content: finalTitle, ...(finalContent !== undefined ? { description: finalContent } : {}), ...(params.assignee_ids !== undefined ? { assignee_ids: params.assignee_ids } : {}), }, }); if (response.status !== 200 || !response.body) { throw new Error("Failed to update todo"); } return { content: [ { type: "text", text: `Todo updated!\n\nID: ${response.body.id}\nContent: ${response.body.content}`, }, ], }; } catch (error) { return { content: [{ type: "text", text: handleBasecampError(error) }], }; } },
- src/tools/todos.ts:281-300 (schema)The input schema and metadata (title, description, annotations) for the 'basecamp_update_todo' tool, defining parameters like bucket_id, todo_id, title, assignee_ids, and content operations.{ title: "Update Basecamp Todo", description: `Update a todo item. Use partial content operations when possible to save on token usage. ${htmlRules}`, inputSchema: { bucket_id: BasecampIdSchema, todo_id: BasecampIdSchema, title: z.string().optional().describe("New todo title"), assignee_ids: z .array(BasecampIdSchema) .optional() .describe("Array of person IDs to assign to this todo"), ...ContentOperationFields, }, annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, },
- src/tools/todos.ts:279-378 (registration)Registers the 'basecamp_update_todo' tool with the MCP server, providing name, schema, annotations, and handler function.server.registerTool( "basecamp_update_todo", { title: "Update Basecamp Todo", description: `Update a todo item. Use partial content operations when possible to save on token usage. ${htmlRules}`, inputSchema: { bucket_id: BasecampIdSchema, todo_id: BasecampIdSchema, title: z.string().optional().describe("New todo title"), assignee_ids: z .array(BasecampIdSchema) .optional() .describe("Array of person IDs to assign to this todo"), ...ContentOperationFields, }, annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, }, async (params) => { try { // Validate at least one operation is provided validateContentOperations(params, ["title", "assignee_ids"]); const client = await initializeBasecampClient(); // Fetch current todo to get existing values const currentResponse = await client.todos.get({ params: { bucketId: params.bucket_id, todoId: params.todo_id, }, }); if (currentResponse.status !== 200 || !currentResponse.body) { throw new Error( `Failed to fetch current todo: ${currentResponse.status}`, ); } // Determine final title (maps to Basecamp's content field) const finalTitle = params.title ?? currentResponse.body.content; // Determine final content (maps to Basecamp's description field) let finalContent: string | undefined; const hasPartialOps = params.content_append || params.content_prepend || params.search_replace; if (hasPartialOps) { const currentContent = currentResponse.body.description || ""; const result = applyContentOperations(currentContent, params); if (result === undefined) { throw new Error("Content operations resulted in undefined content"); } finalContent = result; } else if (params.content !== undefined) { // Full content replacement finalContent = params.content; } const response = await client.todos.update({ params: { bucketId: params.bucket_id, todoId: params.todo_id, }, body: { content: finalTitle, ...(finalContent !== undefined ? { description: finalContent } : {}), ...(params.assignee_ids !== undefined ? { assignee_ids: params.assignee_ids } : {}), }, }); if (response.status !== 200 || !response.body) { throw new Error("Failed to update todo"); } return { content: [ { type: "text", text: `Todo updated!\n\nID: ${response.body.id}\nContent: ${response.body.content}`, }, ], }; } catch (error) { return { content: [{ type: "text", text: handleBasecampError(error) }], }; } }, );