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
| Name | Required | Description | Default |
|---|---|---|---|
| bucket_id | Yes | Basecamp resource identifier | |
| todo_id | Yes |
Implementation Reference
- src/tools/todos.ts:227-242 (handler)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) }], }; } },
- src/tools/todos.ts:216-219 (schema)Input schema definition for the tool parameters bucket_id and todo_id.inputSchema: { bucket_id: BasecampIdSchema, todo_id: BasecampIdSchema, },
- src/schemas/common.ts:10-12 (schema)Reusable Zod schema for Basecamp IDs used in the tool's input schema.export const BasecampIdSchema = z .number() .describe("Basecamp resource identifier");
- src/tools/todos.ts:211-243 (registration)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);
- src/utils/auth.ts:24-64 (helper)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, }); }
- src/utils/errorHandlers.ts:11-89 (helper)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)}`; }