Skip to main content
Glama

basecamp_update_message

Modify existing Basecamp messages by updating subjects, replacing content, or appending/prepending text using HTML formatting rules and partial operations to optimize token usage.

Instructions

Update a message. 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
message_idYes
subjectNoNew message subject
message_type_idNoOptional message type/category ID
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

  • The handler function implementing the core logic for updating a Basecamp message. Supports partial updates via content operations (append, prepend, search-replace), fetches current content if needed, applies changes, and calls the Basecamp API.
    async (params) => {
      try {
        // Validate at least one operation is provided
        validateContentOperations(params, ["subject", "message_type_id"]);
    
        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 message if needed for partial operations
          if (hasPartialOps) {
            const currentResponse = await client.messages.get({
              params: {
                bucketId: params.bucket_id,
                messageId: params.message_id,
              },
            });
    
            if (currentResponse.status !== 200 || !currentResponse.body) {
              throw new Error(
                `Failed to fetch current message for partial update: ${currentResponse.status}`,
              );
            }
    
            const currentContent = currentResponse.body.content || "";
            finalContent = applyContentOperations(currentContent, params);
          } else {
            // Full content replacement
            finalContent = params.content;
          }
        }
    
        const response = await client.messages.update({
          params: { bucketId: params.bucket_id, messageId: params.message_id },
          body: {
            ...(params.subject ? { subject: params.subject } : {}),
            ...(finalContent !== undefined ? { content: finalContent } : {}),
            ...(params.message_type_id
              ? { category_id: params.message_type_id }
              : {}),
          },
        });
    
        if (response.status !== 200 || !response.body) {
          throw new Error(`Failed to update message`);
        }
    
        return {
          content: [
            {
              type: "text",
              text: `Message updated successfully!\n\nID: ${response.body.id}\nSubject: ${response.body.title}`,
            },
          ],
        };
      } catch (error) {
        return {
          content: [{ type: "text", text: handleBasecampError(error) }],
        };
      }
    },
  • The input schema for the basecamp_update_message tool, defining parameters like bucket_id, message_id, subject, message_type_id, and content operations using Zod.
    inputSchema: {
      bucket_id: BasecampIdSchema,
      message_id: BasecampIdSchema,
      subject: z
        .string()
        .min(1)
        .max(500)
        .optional()
        .describe("New message subject"),
      message_type_id: BasecampIdSchema.optional().describe(
        "Optional message type/category ID",
      ),
      ...ContentOperationFields,
    },
  • Key helper function that applies partial content operations (full replace, append, prepend, search-replace) to existing message content, enabling efficient updates without full content resubmission.
    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;
    }
  • Helper function to validate that at least one content operation or additional field (like subject) is provided for the update.
    export function validateContentOperations(
      operations: ContentOperationParams,
      additionalFields: string[] = [],
    ): void {
      const hasContentOp =
        operations.content ||
        operations.content_append ||
        operations.content_prepend ||
        operations.search_replace;
    
      const hasAdditionalFields = additionalFields.some(
        (field) => (operations as Record<string, unknown>)[field] !== undefined,
      );
    
      if (!hasContentOp && !hasAdditionalFields) {
        const fieldsStr = [
          "content",
          "partial operations",
          ...additionalFields,
        ].join(", ");
        throw new Error(`At least one field (${fieldsStr}) must be provided`);
      }
    }
  • Shared Zod schema fields for content operations, spread into the tool's inputSchema to support partial updates.
    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.",
        ),
    };

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