Skip to main content
Glama

basecamp_update_comment

Modify existing comments in Basecamp projects by updating, appending, or replacing content using HTML formatting rules and partial operations to optimize token usage.

Instructions

Update a comment. 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

TableJSON Schema
NameRequiredDescriptionDefault
bucket_idYesBasecamp resource identifier
comment_idYes
contentNoIf provided, replaces entire HTML content. Cannot be used with content_append, content_prepend, or search_replace.
content_appendNoText to append to the end of current content. Cannot be used with content.
content_prependNoText to prepend to the beginning of current content. Cannot be used with content.
search_replaceNoArray of search-replace operations to apply to current content. Cannot be used with content.

Implementation Reference

  • Core execution logic for the basecamp_update_comment tool: validates operations, handles partial/full updates by fetching current content if necessary, applies transformations, calls Basecamp API to update, and returns success/error messages.
    async (params) => { try { // Validate at least one operation is provided validateContentOperations(params); const client = await initializeBasecampClient(); let finalContent: string | undefined; // Check if we need to fetch current content for partial operations const hasPartialOps = params.content_append || params.content_prepend || params.search_replace; if (hasPartialOps || params.content !== undefined) { // Fetch current comment if needed for partial operations if (hasPartialOps) { const currentResponse = await client.comments.get({ params: { bucketId: params.bucket_id, commentId: params.comment_id, }, }); if (currentResponse.status !== 200 || !currentResponse.body) { throw new Error( `Failed to fetch current comment: ${currentResponse.status}`, ); } const currentContent = currentResponse.body.content || ""; finalContent = applyContentOperations(currentContent, params); } else { // Full content replacement finalContent = params.content; } } // If no content changes (shouldn't happen due to validation, but be safe) if (finalContent === undefined) { throw new Error("No content operations resulted in changes"); } // Update the comment const response = await client.comments.update({ params: { bucketId: params.bucket_id, commentId: params.comment_id, }, body: { content: finalContent }, }); if (response.status !== 200 || !response.body) { throw new Error("Failed to update comment"); } return { content: [ { type: "text", text: `Comment updated successfully!\n\nID: ${response.body.id}`, }, ], }; } catch (error) { return { content: [{ type: "text", text: handleBasecampError(error) }], }; }
  • Registration of the basecamp_update_comment tool within registerCommentTools, including title, description, input schema (bucket_id, comment_id, content operations), annotations, and inline handler.
    server.registerTool( "basecamp_update_comment", { title: "Update Basecamp Comment", description: `Update a comment. Use partial content operations when possible to save on token usage. ${htmlRules}`, inputSchema: { bucket_id: BasecampIdSchema, comment_id: BasecampIdSchema, ...ContentOperationFields, }, annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true, }, }, async (params) => { try { // Validate at least one operation is provided validateContentOperations(params); const client = await initializeBasecampClient(); let finalContent: string | undefined; // Check if we need to fetch current content for partial operations const hasPartialOps = params.content_append || params.content_prepend || params.search_replace; if (hasPartialOps || params.content !== undefined) { // Fetch current comment if needed for partial operations if (hasPartialOps) { const currentResponse = await client.comments.get({ params: { bucketId: params.bucket_id, commentId: params.comment_id, }, }); if (currentResponse.status !== 200 || !currentResponse.body) { throw new Error( `Failed to fetch current comment: ${currentResponse.status}`, ); } const currentContent = currentResponse.body.content || ""; finalContent = applyContentOperations(currentContent, params); } else { // Full content replacement finalContent = params.content; } } // If no content changes (shouldn't happen due to validation, but be safe) if (finalContent === undefined) { throw new Error("No content operations resulted in changes"); } // Update the comment const response = await client.comments.update({ params: { bucketId: params.bucket_id, commentId: params.comment_id, }, body: { content: finalContent }, }); if (response.status !== 200 || !response.body) { throw new Error("Failed to update comment"); } return { content: [ { type: "text", text: `Comment updated successfully!\n\nID: ${response.body.id}`, }, ], }; } catch (error) { return { content: [{ type: "text", text: handleBasecampError(error) }], }; } }, );
  • Shared schema fields for content operations (full replace, append, prepend, search-replace) spread into the tool's inputSchema.
    export const ContentOperationFields = { content: z .string() .optional() .describe( `If provided, replaces entire HTML content. Cannot be used with content_append, content_prepend, or search_replace.`, ), content_append: z .string() .optional() .describe( "Text to append to the end of current content. Cannot be used with content.", ), content_prepend: z .string() .optional() .describe( "Text to prepend to the beginning of current content. Cannot be used with content.", ), search_replace: z .array( z.object({ find: z.string().describe("Text to search for"), replace: z .string() .describe("Text to replace ALL the occurrences with"), }), ) .optional() .describe( "Array of search-replace operations to apply to current content. Cannot be used with content.", ), };
  • Common Zod schema for Basecamp IDs used in bucket_id and comment_id parameters.
    export const BasecampIdSchema = z .number() .describe("Basecamp resource identifier");
  • Helper function to apply content operations (full or partial) to existing comment content, used in the handler.
    export function applyContentOperations( currentContent: string, operations: ContentOperationParams, ): string | undefined { const hasPartialOps = operations.content_append || operations.content_prepend || operations.search_replace; // Validate mutual exclusivity if (operations.content && hasPartialOps) { throw new Error( "Cannot use 'content' with partial operations (content_append, content_prepend, search_replace). Use either full replacement or partial operations, not both.", ); } // If full content replacement, return it directly if (operations.content !== undefined) { return operations.content; } // If no operations at all, return undefined (no changes) if (!hasPartialOps) { return undefined; } // Apply partial operations let finalContent = currentContent; // Apply search-replace operations first if (operations.search_replace) { for (const operation of operations.search_replace) { // Check if the search string exists in the content if (!finalContent.includes(operation.find)) { throw new Error( `Search string not found: "${operation.find}". The content does not contain this text.`, ); } finalContent = finalContent.replaceAll(operation.find, operation.replace); } } // Apply prepend if (operations.content_prepend) { finalContent = operations.content_prepend + finalContent; } // Apply append if (operations.content_append) { finalContent = finalContent + operations.content_append; } return finalContent; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/stefanoverna/basecamp-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server