Skip to main content
Glama

Basecamp MCP Server

by jhliberty
basecamp-client.ts14.5 kB
import axios, { AxiosInstance, AxiosResponse } from 'axios'; import type { BasecampProject, TodoList, Todo, Card, CardTable, Column, CardStep, Comment, CampfireLine, Message, Document, Upload, Webhook, DailyCheckIn, QuestionAnswer, OAuthTokens, AuthMode, } from '../types/basecamp.js'; export class BasecampClient { private client: AxiosInstance; private accountId: string; private userAgent: string; private authMode: AuthMode; constructor(options: { username?: string; password?: string; accessToken?: string; accountId: string; userAgent: string; authMode?: AuthMode; }) { this.accountId = options.accountId; this.userAgent = options.userAgent; this.authMode = options.authMode || 'basic'; const baseURL = `https://3.basecampapi.com/${this.accountId}`; this.client = axios.create({ baseURL, headers: { 'User-Agent': this.userAgent, 'Content-Type': 'application/json', }, timeout: 30000, }); // Set up authentication if (this.authMode === 'basic') { if (!options.username || !options.password) { throw new Error('Username and password required for basic auth'); } this.client.defaults.auth = { username: options.username, password: options.password, }; } else if (this.authMode === 'oauth') { if (!options.accessToken) { throw new Error('Access token required for OAuth auth'); } this.client.defaults.headers.common['Authorization'] = `Bearer ${options.accessToken}`; } } async testConnection(): Promise<{ success: boolean; message: string }> { try { await this.getProjects(); return { success: true, message: 'Connection successful' }; } catch (error) { return { success: false, message: `Connection failed: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } // Project methods async getProjects(): Promise<BasecampProject[]> { const response = await this.client.get('/projects.json'); return response.data; } async getProject(projectId: string): Promise<BasecampProject> { const response = await this.client.get(`/projects/${projectId}.json`); return response.data; } // Todo methods async getTodoLists(projectId: string): Promise<TodoList[]> { // First get the project to find the todoset const project = await this.getProject(projectId); const todoset = project.dock.find(item => item.name === 'todoset'); if (!todoset) { throw new Error(`No todoset found for project ${projectId}`); } const response = await this.client.get(`/buckets/${projectId}/todosets/${todoset.id}/todolists.json`); return response.data; } async getTodos(projectId: string, todolistId: string): Promise<Todo[]> { const response = await this.client.get(`/buckets/${projectId}/todolists/${todolistId}/todos.json`); return response.data; } async getTodo(todoId: string): Promise<Todo> { const response = await this.client.get(`/todos/${todoId}.json`); return response.data; } // Card Table methods async getCardTables(projectId: string): Promise<any[]> { const project = await this.getProject(projectId); return project.dock.filter(item => item.name === 'kanban_board' || item.name === 'card_table' ); } async getCardTable(projectId: string): Promise<any> { const cardTables = await this.getCardTables(projectId); if (!cardTables.length) { throw new Error(`No card tables found for project: ${projectId}`); } return cardTables[0]; } async getCardTableDetails(projectId: string, cardTableId: string): Promise<CardTable> { try { const response = await this.client.get(`/buckets/${projectId}/card_tables/${cardTableId}.json`); return response.data; } catch (error: any) { if (error.response?.status === 204) { return { id: cardTableId, title: 'Card Table', lists: [], status: 'empty' }; } throw error; } } // Column methods async getColumns(projectId: string, cardTableId: string): Promise<Column[]> { const cardTableDetails = await this.getCardTableDetails(projectId, cardTableId); return cardTableDetails.lists || []; } async getColumn(projectId: string, columnId: string): Promise<Column> { const response = await this.client.get(`/buckets/${projectId}/card_tables/columns/${columnId}.json`); return response.data; } async createColumn(projectId: string, cardTableId: string, title: string): Promise<Column> { const response = await this.client.post(`/buckets/${projectId}/card_tables/${cardTableId}/columns.json`, { title, }); return response.data; } async updateColumn(projectId: string, columnId: string, title: string): Promise<Column> { const response = await this.client.put(`/buckets/${projectId}/card_tables/columns/${columnId}.json`, { title, }); return response.data; } async moveColumn(projectId: string, columnId: string, position: number, cardTableId: string): Promise<void> { await this.client.post(`/buckets/${projectId}/card_tables/${cardTableId}/moves.json`, { source_id: columnId, target_id: cardTableId, position, }); } async updateColumnColor(projectId: string, columnId: string, color: string): Promise<Column> { const response = await this.client.patch(`/buckets/${projectId}/card_tables/columns/${columnId}/color.json`, { color, }); return response.data; } async putColumnOnHold(projectId: string, columnId: string): Promise<void> { await this.client.post(`/buckets/${projectId}/card_tables/columns/${columnId}/on_hold.json`); } async removeColumnHold(projectId: string, columnId: string): Promise<void> { await this.client.delete(`/buckets/${projectId}/card_tables/columns/${columnId}/on_hold.json`); } async watchColumn(projectId: string, columnId: string): Promise<void> { await this.client.post(`/buckets/${projectId}/card_tables/lists/${columnId}/subscription.json`); } async unwatchColumn(projectId: string, columnId: string): Promise<void> { await this.client.delete(`/buckets/${projectId}/card_tables/lists/${columnId}/subscription.json`); } // Card methods async getCards(projectId: string, columnId: string): Promise<Card[]> { const response = await this.client.get(`/buckets/${projectId}/card_tables/lists/${columnId}/cards.json`); return response.data; } async getCard(projectId: string, cardId: string): Promise<Card> { const response = await this.client.get(`/buckets/${projectId}/card_tables/cards/${cardId}.json`); return response.data; } async createCard( projectId: string, columnId: string, title: string, content?: string, dueOn?: string, notify = false ): Promise<Card> { const data: any = { title }; if (content) data.content = content; if (dueOn) data.due_on = dueOn; if (notify) data.notify = notify; const response = await this.client.post(`/buckets/${projectId}/card_tables/lists/${columnId}/cards.json`, data); return response.data; } async updateCard( projectId: string, cardId: string, title?: string, content?: string, dueOn?: string, assigneeIds?: string[] ): Promise<Card> { const data: any = {}; if (title) data.title = title; if (content) data.content = content; if (dueOn) data.due_on = dueOn; if (assigneeIds) data.assignee_ids = assigneeIds; const response = await this.client.put(`/buckets/${projectId}/card_tables/cards/${cardId}.json`, data); return response.data; } async moveCard(projectId: string, cardId: string, columnId: string): Promise<void> { await this.client.post(`/buckets/${projectId}/card_tables/cards/${cardId}/moves.json`, { column_id: columnId, }); } async completeCard(projectId: string, cardId: string): Promise<any> { const response = await this.client.post(`/buckets/${projectId}/todos/${cardId}/completion.json`); return response.data; } async uncompleteCard(projectId: string, cardId: string): Promise<void> { await this.client.delete(`/buckets/${projectId}/todos/${cardId}/completion.json`); } // Card Step methods async getCardSteps(projectId: string, cardId: string): Promise<CardStep[]> { const card = await this.getCard(projectId, cardId); return card.steps || []; } async createCardStep( projectId: string, cardId: string, title: string, dueOn?: string, assigneeIds?: string[] ): Promise<CardStep> { const data: any = { title }; if (dueOn) data.due_on = dueOn; if (assigneeIds) data.assignee_ids = assigneeIds; const response = await this.client.post(`/buckets/${projectId}/card_tables/cards/${cardId}/steps.json`, data); return response.data; } async getCardStep(projectId: string, stepId: string): Promise<CardStep> { const response = await this.client.get(`/buckets/${projectId}/card_tables/steps/${stepId}.json`); return response.data; } async updateCardStep( projectId: string, stepId: string, title?: string, dueOn?: string, assigneeIds?: string[] ): Promise<CardStep> { const data: any = {}; if (title) data.title = title; if (dueOn) data.due_on = dueOn; if (assigneeIds) data.assignee_ids = assigneeIds; const response = await this.client.put(`/buckets/${projectId}/card_tables/steps/${stepId}.json`, data); return response.data; } async deleteCardStep(projectId: string, stepId: string): Promise<void> { await this.client.delete(`/buckets/${projectId}/card_tables/steps/${stepId}.json`); } async completeCardStep(projectId: string, stepId: string): Promise<any> { const response = await this.client.post(`/buckets/${projectId}/todos/${stepId}/completion.json`); return response.data; } async uncompleteCardStep(projectId: string, stepId: string): Promise<void> { await this.client.delete(`/buckets/${projectId}/todos/${stepId}/completion.json`); } // Communication methods async getCampfireLines(projectId: string, campfireId: string): Promise<CampfireLine[]> { const response = await this.client.get(`/buckets/${projectId}/chats/${campfireId}/lines.json`); return response.data; } async getComments(projectId: string, recordingId: string): Promise<Comment[]> { const response = await this.client.get(`/buckets/${projectId}/recordings/${recordingId}/comments.json`); return response.data; } // Document methods async getDocuments(projectId: string, vaultId: string): Promise<Document[]> { const response = await this.client.get(`/buckets/${projectId}/vaults/${vaultId}/documents.json`); return response.data; } async getDocument(projectId: string, documentId: string): Promise<Document> { const response = await this.client.get(`/buckets/${projectId}/documents/${documentId}.json`); return response.data; } async createDocument( projectId: string, vaultId: string, title: string, content: string, status = 'active' ): Promise<Document> { const response = await this.client.post(`/buckets/${projectId}/vaults/${vaultId}/documents.json`, { title, content, status, }); return response.data; } async updateDocument( projectId: string, documentId: string, title?: string, content?: string ): Promise<Document> { const data: any = {}; if (title) data.title = title; if (content) data.content = content; const response = await this.client.put(`/buckets/${projectId}/documents/${documentId}.json`, data); return response.data; } async trashDocument(projectId: string, documentId: string): Promise<void> { await this.client.delete(`/buckets/${projectId}/documents/${documentId}.json`); } // File methods async getUploads(projectId: string, vaultId?: string): Promise<Upload[]> { const endpoint = vaultId ? `/buckets/${projectId}/vaults/${vaultId}/uploads.json` : `/buckets/${projectId}/uploads.json`; const response = await this.client.get(endpoint); return response.data; } async getUpload(projectId: string, uploadId: string): Promise<Upload> { const response = await this.client.get(`/buckets/${projectId}/uploads/${uploadId}.json`); return response.data; } async createAttachment(filePath: string, name: string, contentType = 'application/octet-stream'): Promise<any> { // Note: This would need actual file handling in a real implementation // For now, returning a placeholder throw new Error('File upload not implemented in TypeScript version yet'); } // Webhook methods async getWebhooks(projectId: string): Promise<Webhook[]> { const response = await this.client.get(`/buckets/${projectId}/webhooks.json`); return response.data; } async createWebhook(projectId: string, payloadUrl: string, types?: string[]): Promise<Webhook> { const data: any = { payload_url: payloadUrl }; if (types) data.types = types; const response = await this.client.post(`/buckets/${projectId}/webhooks.json`, data); return response.data; } async deleteWebhook(projectId: string, webhookId: string): Promise<void> { await this.client.delete(`/buckets/${projectId}/webhooks/${webhookId}.json`); } // Check-in methods async getDailyCheckIns(projectId: string, page = 1): Promise<DailyCheckIn[]> { const project = await this.getProject(projectId); const questionnaire = project.dock.find(item => item.name === 'questionnaire'); if (!questionnaire) { throw new Error(`No questionnaire found for project ${projectId}`); } const response = await this.client.get( `/buckets/${projectId}/questionnaires/${questionnaire.id}/questions.json`, { params: { page } } ); return response.data; } async getQuestionAnswers(projectId: string, questionId: string, page = 1): Promise<QuestionAnswer[]> { const response = await this.client.get( `/buckets/${projectId}/questions/${questionId}/answers.json`, { params: { page } } ); return response.data; } // Utility methods async getEvents(projectId: string, recordingId: string): Promise<any[]> { const response = await this.client.get(`/buckets/${projectId}/recordings/${recordingId}/events.json`); return response.data; } }

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/jhliberty/basecamp-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server