Refactor card management in Basecamp client and MCP server CLI
- Rename and enhance methods for retrieving card tables and managing cards. - Introduce new methods for completing and uncompleting cards and card steps. - Update MCP server CLI to include new card and card step functionalities. - Revise README to reflect changes in card management tools and usage examples.
This commit is contained in:
@@ -351,30 +351,38 @@ class BasecampClient:
|
||||
return response.json()
|
||||
|
||||
# Card Table methods
|
||||
def get_card_table(self, project_id):
|
||||
"""Get the card table for a project (Basecamp 3 has one card table per project)."""
|
||||
def get_card_tables(self, project_id):
|
||||
"""Get all card tables for a project."""
|
||||
project = self.get_project(project_id)
|
||||
try:
|
||||
return next(_ for _ in project["dock"] if _["name"] == "card_table")
|
||||
except (IndexError, TypeError, StopIteration):
|
||||
raise Exception(f"Failed to get card table for project: {project_id}. Project response: {project}")
|
||||
return [_ for _ in project["dock"] if _["name"] == "kanban_board"]
|
||||
except (IndexError, TypeError):
|
||||
return []
|
||||
|
||||
def get_card_table(self, project_id):
|
||||
"""Get the first card table for a project (Basecamp 3 can have multiple card tables per project)."""
|
||||
card_tables = self.get_card_tables(project_id)
|
||||
if not card_tables:
|
||||
raise Exception(f"No card tables found for project: {project_id}")
|
||||
return card_tables[0] # Return the first card table
|
||||
|
||||
def get_card_table_details(self, project_id, card_table_id):
|
||||
"""Get details for a specific card table."""
|
||||
response = self.get(f'buckets/{project_id}/card_tables/{card_table_id}.json')
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
elif response.status_code == 204:
|
||||
# 204 means "No Content" - return an empty structure
|
||||
return {"lists": [], "id": card_table_id, "status": "empty"}
|
||||
else:
|
||||
raise Exception(f"Failed to get card table: {response.status_code} - {response.text}")
|
||||
|
||||
# Card Table Column methods
|
||||
def get_columns(self, project_id, card_table_id):
|
||||
"""Get all columns in a card table."""
|
||||
response = self.get(f'buckets/{project_id}/card_tables/{card_table_id}/columns.json')
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to get columns: {response.status_code} - {response.text}")
|
||||
# Get the card table details which includes the lists (columns)
|
||||
card_table_details = self.get_card_table_details(project_id, card_table_id)
|
||||
return card_table_details.get('lists', [])
|
||||
|
||||
def get_column(self, project_id, column_id):
|
||||
"""Get a specific column."""
|
||||
@@ -402,10 +410,14 @@ class BasecampClient:
|
||||
else:
|
||||
raise Exception(f"Failed to update column: {response.status_code} - {response.text}")
|
||||
|
||||
def move_column(self, project_id, column_id, position):
|
||||
def move_column(self, project_id, column_id, position, card_table_id):
|
||||
"""Move a column to a new position."""
|
||||
data = {"position": position}
|
||||
response = self.post(f'buckets/{project_id}/card_tables/columns/{column_id}/moves.json', data)
|
||||
data = {
|
||||
"source_id": column_id,
|
||||
"target_id": card_table_id,
|
||||
"position": position
|
||||
}
|
||||
response = self.post(f'buckets/{project_id}/card_tables/{card_table_id}/moves.json', data)
|
||||
if response.status_code == 204:
|
||||
return True
|
||||
else:
|
||||
@@ -469,39 +481,125 @@ class BasecampClient:
|
||||
else:
|
||||
raise Exception(f"Failed to get card: {response.status_code} - {response.text}")
|
||||
|
||||
def create_card(self, project_id, column_id, title, content=None):
|
||||
def create_card(self, project_id, column_id, title, content=None, due_on=None, notify=False):
|
||||
"""Create a new card in a column."""
|
||||
data = {"title": title}
|
||||
if content:
|
||||
data["content"] = content
|
||||
if due_on:
|
||||
data["due_on"] = due_on
|
||||
if notify:
|
||||
data["notify"] = notify
|
||||
response = self.post(f'buckets/{project_id}/card_tables/lists/{column_id}/cards.json', data)
|
||||
if response.status_code == 201:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to create card: {response.status_code} - {response.text}")
|
||||
|
||||
def update_card(self, project_id, card_id, title=None, content=None):
|
||||
def update_card(self, project_id, card_id, title=None, content=None, due_on=None, assignee_ids=None):
|
||||
"""Update a card."""
|
||||
data = {}
|
||||
if title:
|
||||
data["title"] = title
|
||||
if content:
|
||||
data["content"] = content
|
||||
if due_on:
|
||||
data["due_on"] = due_on
|
||||
if assignee_ids:
|
||||
data["assignee_ids"] = assignee_ids
|
||||
response = self.put(f'buckets/{project_id}/card_tables/cards/{card_id}.json', data)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to update card: {response.status_code} - {response.text}")
|
||||
|
||||
def move_card(self, project_id, card_id, column_id=None, position=None):
|
||||
"""Move a card to a new column and/or position."""
|
||||
data = {}
|
||||
if column_id:
|
||||
data["column_id"] = column_id
|
||||
if position:
|
||||
data["position"] = position
|
||||
def move_card(self, project_id, card_id, column_id):
|
||||
"""Move a card to a new column."""
|
||||
data = {"column_id": column_id}
|
||||
response = self.post(f'buckets/{project_id}/card_tables/cards/{card_id}/moves.json', data)
|
||||
if response.status_code == 204:
|
||||
return True
|
||||
else:
|
||||
raise Exception(f"Failed to move card: {response.status_code} - {response.text}")
|
||||
|
||||
def complete_card(self, project_id, card_id):
|
||||
"""Mark a card as complete."""
|
||||
response = self.post(f'buckets/{project_id}/todos/{card_id}/completion.json')
|
||||
if response.status_code == 201:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to complete card: {response.status_code} - {response.text}")
|
||||
|
||||
def uncomplete_card(self, project_id, card_id):
|
||||
"""Mark a card as incomplete."""
|
||||
response = self.delete(f'buckets/{project_id}/todos/{card_id}/completion.json')
|
||||
if response.status_code == 204:
|
||||
return True
|
||||
else:
|
||||
raise Exception(f"Failed to uncomplete card: {response.status_code} - {response.text}")
|
||||
|
||||
# Card Steps methods
|
||||
def get_card_steps(self, project_id, card_id):
|
||||
"""Get all steps (sub-tasks) for a card."""
|
||||
card = self.get_card(project_id, card_id)
|
||||
return card.get('steps', [])
|
||||
|
||||
def create_card_step(self, project_id, card_id, title, due_on=None, assignee_ids=None):
|
||||
"""Create a new step (sub-task) for a card."""
|
||||
data = {"title": title}
|
||||
if due_on:
|
||||
data["due_on"] = due_on
|
||||
if assignee_ids:
|
||||
data["assignee_ids"] = assignee_ids
|
||||
response = self.post(f'buckets/{project_id}/card_tables/cards/{card_id}/steps.json', data)
|
||||
if response.status_code == 201:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to create card step: {response.status_code} - {response.text}")
|
||||
|
||||
def get_card_step(self, project_id, step_id):
|
||||
"""Get a specific card step."""
|
||||
response = self.get(f'buckets/{project_id}/card_tables/steps/{step_id}.json')
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to get card step: {response.status_code} - {response.text}")
|
||||
|
||||
def update_card_step(self, project_id, step_id, title=None, due_on=None, assignee_ids=None):
|
||||
"""Update a card step."""
|
||||
data = {}
|
||||
if title:
|
||||
data["title"] = title
|
||||
if due_on:
|
||||
data["due_on"] = due_on
|
||||
if assignee_ids:
|
||||
data["assignee_ids"] = assignee_ids
|
||||
response = self.put(f'buckets/{project_id}/card_tables/steps/{step_id}.json', data)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to update card step: {response.status_code} - {response.text}")
|
||||
|
||||
def delete_card_step(self, project_id, step_id):
|
||||
"""Delete a card step."""
|
||||
response = self.delete(f'buckets/{project_id}/card_tables/steps/{step_id}.json')
|
||||
if response.status_code == 204:
|
||||
return True
|
||||
else:
|
||||
raise Exception(f"Failed to delete card step: {response.status_code} - {response.text}")
|
||||
|
||||
def complete_card_step(self, project_id, step_id):
|
||||
"""Mark a card step as complete."""
|
||||
response = self.post(f'buckets/{project_id}/todos/{step_id}/completion.json')
|
||||
if response.status_code == 201:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to complete card step: {response.status_code} - {response.text}")
|
||||
|
||||
def uncomplete_card_step(self, project_id, step_id):
|
||||
"""Mark a card step as incomplete."""
|
||||
response = self.delete(f'buckets/{project_id}/todos/{step_id}/completion.json')
|
||||
if response.status_code == 204:
|
||||
return True
|
||||
else:
|
||||
raise Exception(f"Failed to uncomplete card step: {response.status_code} - {response.text}")
|
||||
|
||||
Reference in New Issue
Block a user