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