Skip to main content
Glama

basecamp_complete_todo

Mark a Basecamp todo as completed using bucket and todo identifiers to track project progress.

Instructions

Mark a todo as completed.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
bucket_idYesBasecamp resource identifier
todo_idYes

Implementation Reference

  • The handler function that executes the tool's core logic: initializes the Basecamp client and calls the complete method on the specified todo.
    async (params) => {
      try {
        const client = await initializeBasecampClient();
        await client.todos.complete({
          params: { bucketId: params.bucket_id, todoId: params.todo_id },
        });
    
        return {
          content: [{ type: "text", text: "Todo marked as completed!" }],
        };
      } catch (error) {
        return {
          content: [{ type: "text", text: handleBasecampError(error) }],
        };
      }
    },
  • Input schema definition for the tool parameters bucket_id and todo_id.
    inputSchema: {
      bucket_id: BasecampIdSchema,
      todo_id: BasecampIdSchema,
    },
  • Reusable Zod schema for Basecamp IDs used in the tool's input schema.
    export const BasecampIdSchema = z
      .number()
      .describe("Basecamp resource identifier");
  • Registration of the basecamp_complete_todo tool including title, description, inputSchema, annotations, and handler.
    server.registerTool(
      "basecamp_complete_todo",
      {
        title: "Complete Basecamp Todo",
        description: "Mark a todo as completed.",
        inputSchema: {
          bucket_id: BasecampIdSchema,
          todo_id: BasecampIdSchema,
        },
        annotations: {
          readOnlyHint: false,
          destructiveHint: false,
          idempotentHint: true,
          openWorldHint: true,
        },
      },
      async (params) => {
        try {
          const client = await initializeBasecampClient();
          await client.todos.complete({
            params: { bucketId: params.bucket_id, todoId: params.todo_id },
          });
    
          return {
            content: [{ type: "text", text: "Todo marked as completed!" }],
          };
        } catch (error) {
          return {
            content: [{ type: "text", text: handleBasecampError(error) }],
          };
        }
      },
    );
  • src/index.ts:63-63 (registration)
    Top-level call to register all todo tools, including basecamp_complete_todo.
    registerTodoTools(server);
  • Helper function to initialize authenticated Basecamp client used in the tool handler.
    export async function initializeBasecampClient(): Promise<Client> {
      // Validate required environment variables
      const requiredEnvVars = [
        "BASECAMP_CLIENT_ID",
        "BASECAMP_CLIENT_SECRET",
        "BASECAMP_REFRESH_TOKEN",
        "BASECAMP_ACCOUNT_ID",
      ];
    
      const missing = requiredEnvVars.filter((varName) => !process.env[varName]);
      if (missing.length > 0) {
        throw new Error(
          `Missing required environment variables: ${missing.join(", ")}. ` +
            `Please set these in your environment or .env file.`,
        );
      }
    
      // Get bearer token (cache if possible to avoid repeated OAuth requests)
      if (!cachedBearerToken) {
        try {
          cachedBearerToken = await getBearerToken({
            clientId: process.env.BASECAMP_CLIENT_ID!,
            clientSecret: process.env.BASECAMP_CLIENT_SECRET!,
            refreshToken: process.env.BASECAMP_REFRESH_TOKEN!,
            userAgent: process.env.BASECAMP_USER_AGENT,
          });
        } catch (error) {
          throw new Error(
            `Failed to obtain Basecamp access token: ${error instanceof Error ? error.message : String(error)}. ` +
              `Check your BASECAMP_CLIENT_ID, BASECAMP_CLIENT_SECRET, and BASECAMP_REFRESH_TOKEN are correct.`,
          );
        }
      }
    
      // Build and return client
      return buildClient({
        bearerToken: cachedBearerToken,
        accountId: process.env.BASECAMP_ACCOUNT_ID!,
        userAgent: process.env.BASECAMP_USER_AGENT,
      });
    }
  • Helper function to format Basecamp API errors for user-friendly responses.
    export function handleBasecampError(error: unknown): string {
      // Handle HTTP response errors
      if (error && typeof error === "object" && "status" in error) {
        const status = (error as { status: number }).status;
    
        switch (status) {
          case 400:
            return (
              "Error: Bad request. Check that all required parameters are provided and formatted correctly. " +
              extractErrorDetails(error)
            );
    
          case 401:
            return (
              "Error: Authentication failed. Your access token may have expired. " +
              "Try restarting the server to refresh the token, or check your BASECAMP_REFRESH_TOKEN is valid."
            );
    
          case 403:
            return (
              "Error: Access denied. You don't have permission to access this resource. " +
              "Check that you're using the correct account ID and that your Basecamp user has access to this project/resource."
            );
    
          case 404:
            return (
              "Error: Resource not found. The requested resource (message, todo, project, etc.) doesn't exist or has been deleted. " +
              "Verify the ID is correct and the resource hasn't been moved to trash."
            );
    
          case 422:
            return (
              "Error: Validation failed. The request data didn't pass Basecamp's validation rules. " +
              extractErrorDetails(error) +
              " Check the data format and required fields."
            );
    
          case 429:
            return (
              "Error: Rate limit exceeded. Too many requests have been made to the Basecamp API. " +
              "Please wait a moment before trying again."
            );
    
          case 500:
          case 502:
          case 503:
          case 504:
            return (
              "Error: Basecamp server error. The Basecamp API is experiencing issues. " +
              "Please try again in a moment."
            );
    
          default:
            return `Error: API request failed with status ${status}. ${extractErrorDetails(error)}`;
        }
      }
    
      // Handle network/connection errors
      if (error && typeof error === "object" && "code" in error) {
        const code = (error as { code: string }).code;
    
        if (code === "ECONNABORTED" || code === "ETIMEDOUT") {
          return (
            "Error: Request timed out. The Basecamp API took too long to respond. " +
            "Please try again."
          );
        }
    
        if (code === "ECONNREFUSED" || code === "ENOTFOUND") {
          return (
            "Error: Unable to connect to Basecamp API. " +
            "Check your internet connection and try again."
          );
        }
      }
    
      // Generic error fallback
      return `Error: ${error instanceof Error ? error.message : String(error)}`;
    }

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