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