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:
15
README.md
15
README.md
@@ -98,7 +98,16 @@ Once configured, you can use these tools in Cursor:
|
|||||||
- `get_card` - Get details for a specific card
|
- `get_card` - Get details for a specific card
|
||||||
- `create_card` - Create a new card in a column
|
- `create_card` - Create a new card in a column
|
||||||
- `update_card` - Update a card
|
- `update_card` - Update a card
|
||||||
- `move_card` - Move a card to a new column and/or position
|
- `move_card` - Move a card to a new column
|
||||||
|
- `complete_card` - Mark a card as complete
|
||||||
|
- `uncomplete_card` - Mark a card as incomplete
|
||||||
|
- `get_card_steps` - Get all steps (sub-tasks) for a card
|
||||||
|
- `create_card_step` - Create a new step (sub-task) for a card
|
||||||
|
- `get_card_step` - Get details for a specific card step
|
||||||
|
- `update_card_step` - Update a card step
|
||||||
|
- `delete_card_step` - Delete a card step
|
||||||
|
- `complete_card_step` - Mark a card step as complete
|
||||||
|
- `uncomplete_card_step` - Mark a card step as incomplete
|
||||||
|
|
||||||
### Example Cursor Usage
|
### Example Cursor Usage
|
||||||
|
|
||||||
@@ -111,6 +120,10 @@ Ask Cursor things like:
|
|||||||
- "Create a new card in the 'In Progress' column"
|
- "Create a new card in the 'In Progress' column"
|
||||||
- "Move this card to the 'Done' column"
|
- "Move this card to the 'Done' column"
|
||||||
- "Update the color of the 'Urgent' column to red"
|
- "Update the color of the 'Urgent' column to red"
|
||||||
|
- "Mark card as complete"
|
||||||
|
- "Show me all steps for this card"
|
||||||
|
- "Create a sub-task for this card"
|
||||||
|
- "Mark this card step as complete"
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
|||||||
@@ -351,30 +351,38 @@ class BasecampClient:
|
|||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
# Card Table methods
|
# Card Table methods
|
||||||
def get_card_table(self, project_id):
|
def get_card_tables(self, project_id):
|
||||||
"""Get the card table for a project (Basecamp 3 has one card table per project)."""
|
"""Get all card tables for a project."""
|
||||||
project = self.get_project(project_id)
|
project = self.get_project(project_id)
|
||||||
try:
|
try:
|
||||||
return next(_ for _ in project["dock"] if _["name"] == "card_table")
|
return [_ for _ in project["dock"] if _["name"] == "kanban_board"]
|
||||||
except (IndexError, TypeError, StopIteration):
|
except (IndexError, TypeError):
|
||||||
raise Exception(f"Failed to get card table for project: {project_id}. Project response: {project}")
|
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):
|
def get_card_table_details(self, project_id, card_table_id):
|
||||||
"""Get details for a specific card table."""
|
"""Get details for a specific card table."""
|
||||||
response = self.get(f'buckets/{project_id}/card_tables/{card_table_id}.json')
|
response = self.get(f'buckets/{project_id}/card_tables/{card_table_id}.json')
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()
|
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:
|
else:
|
||||||
raise Exception(f"Failed to get card table: {response.status_code} - {response.text}")
|
raise Exception(f"Failed to get card table: {response.status_code} - {response.text}")
|
||||||
|
|
||||||
# Card Table Column methods
|
# Card Table Column methods
|
||||||
def get_columns(self, project_id, card_table_id):
|
def get_columns(self, project_id, card_table_id):
|
||||||
"""Get all columns in a card table."""
|
"""Get all columns in a card table."""
|
||||||
response = self.get(f'buckets/{project_id}/card_tables/{card_table_id}/columns.json')
|
# Get the card table details which includes the lists (columns)
|
||||||
if response.status_code == 200:
|
card_table_details = self.get_card_table_details(project_id, card_table_id)
|
||||||
return response.json()
|
return card_table_details.get('lists', [])
|
||||||
else:
|
|
||||||
raise Exception(f"Failed to get columns: {response.status_code} - {response.text}")
|
|
||||||
|
|
||||||
def get_column(self, project_id, column_id):
|
def get_column(self, project_id, column_id):
|
||||||
"""Get a specific column."""
|
"""Get a specific column."""
|
||||||
@@ -402,10 +410,14 @@ class BasecampClient:
|
|||||||
else:
|
else:
|
||||||
raise Exception(f"Failed to update column: {response.status_code} - {response.text}")
|
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."""
|
"""Move a column to a new position."""
|
||||||
data = {"position": position}
|
data = {
|
||||||
response = self.post(f'buckets/{project_id}/card_tables/columns/{column_id}/moves.json', 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:
|
if response.status_code == 204:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@@ -469,39 +481,125 @@ class BasecampClient:
|
|||||||
else:
|
else:
|
||||||
raise Exception(f"Failed to get card: {response.status_code} - {response.text}")
|
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."""
|
"""Create a new card in a column."""
|
||||||
data = {"title": title}
|
data = {"title": title}
|
||||||
if content:
|
if content:
|
||||||
data["content"] = 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)
|
response = self.post(f'buckets/{project_id}/card_tables/lists/{column_id}/cards.json', data)
|
||||||
if response.status_code == 201:
|
if response.status_code == 201:
|
||||||
return response.json()
|
return response.json()
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Failed to create card: {response.status_code} - {response.text}")
|
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."""
|
"""Update a card."""
|
||||||
data = {}
|
data = {}
|
||||||
if title:
|
if title:
|
||||||
data["title"] = title
|
data["title"] = title
|
||||||
if content:
|
if content:
|
||||||
data["content"] = 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)
|
response = self.put(f'buckets/{project_id}/card_tables/cards/{card_id}.json', data)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()
|
return response.json()
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Failed to update card: {response.status_code} - {response.text}")
|
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):
|
def move_card(self, project_id, card_id, column_id):
|
||||||
"""Move a card to a new column and/or position."""
|
"""Move a card to a new column."""
|
||||||
data = {}
|
data = {"column_id": column_id}
|
||||||
if column_id:
|
|
||||||
data["column_id"] = column_id
|
|
||||||
if position:
|
|
||||||
data["position"] = position
|
|
||||||
response = self.post(f'buckets/{project_id}/card_tables/cards/{card_id}/moves.json', data)
|
response = self.post(f'buckets/{project_id}/card_tables/cards/{card_id}/moves.json', data)
|
||||||
if response.status_code == 204:
|
if response.status_code == 204:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Failed to move card: {response.status_code} - {response.text}")
|
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}")
|
||||||
|
|||||||
@@ -161,6 +161,17 @@ class MCPServer:
|
|||||||
"required": ["project_id", "question_id"]
|
"required": ["project_id", "question_id"]
|
||||||
},
|
},
|
||||||
# Card Table tools
|
# Card Table tools
|
||||||
|
{
|
||||||
|
"name": "get_card_tables",
|
||||||
|
"description": "Get all card tables for a project",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_id": {"type": "string", "description": "The project ID"}
|
||||||
|
},
|
||||||
|
"required": ["project_id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "get_card_table",
|
"name": "get_card_table",
|
||||||
"description": "Get the card table details for a project",
|
"description": "Get the card table details for a project",
|
||||||
@@ -329,7 +340,9 @@ class MCPServer:
|
|||||||
"project_id": {"type": "string", "description": "The project ID"},
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
"column_id": {"type": "string", "description": "The column ID"},
|
"column_id": {"type": "string", "description": "The column ID"},
|
||||||
"title": {"type": "string", "description": "The card title"},
|
"title": {"type": "string", "description": "The card title"},
|
||||||
"content": {"type": "string", "description": "Optional card content/description"}
|
"content": {"type": "string", "description": "Optional card content/description"},
|
||||||
|
"due_on": {"type": "string", "description": "Optional due date (ISO 8601 format)"},
|
||||||
|
"notify": {"type": "boolean", "description": "Whether to notify assignees (default: false)"}
|
||||||
},
|
},
|
||||||
"required": ["project_id", "column_id", "title"]
|
"required": ["project_id", "column_id", "title"]
|
||||||
}
|
}
|
||||||
@@ -343,24 +356,139 @@ class MCPServer:
|
|||||||
"project_id": {"type": "string", "description": "The project ID"},
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
"card_id": {"type": "string", "description": "The card ID"},
|
"card_id": {"type": "string", "description": "The card ID"},
|
||||||
"title": {"type": "string", "description": "The new card title"},
|
"title": {"type": "string", "description": "The new card title"},
|
||||||
"content": {"type": "string", "description": "The new card content/description"}
|
"content": {"type": "string", "description": "The new card content/description"},
|
||||||
|
"due_on": {"type": "string", "description": "Due date (ISO 8601 format)"},
|
||||||
|
"assignee_ids": {"type": "array", "items": {"type": "string"}, "description": "Array of person IDs to assign to the card"}
|
||||||
},
|
},
|
||||||
"required": ["project_id", "card_id"]
|
"required": ["project_id", "card_id"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "move_card",
|
"name": "move_card",
|
||||||
"description": "Move a card to a new column and/or position",
|
"description": "Move a card to a new column",
|
||||||
"inputSchema": {
|
"inputSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"project_id": {"type": "string", "description": "The project ID"},
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
"card_id": {"type": "string", "description": "The card ID"},
|
"card_id": {"type": "string", "description": "The card ID"},
|
||||||
"column_id": {"type": "string", "description": "The destination column ID (optional)"},
|
"column_id": {"type": "string", "description": "The destination column ID"}
|
||||||
"position": {"type": "integer", "description": "The new 1-based position (optional)"}
|
},
|
||||||
|
"required": ["project_id", "card_id", "column_id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "complete_card",
|
||||||
|
"description": "Mark a card as complete",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
|
"card_id": {"type": "string", "description": "The card ID"}
|
||||||
},
|
},
|
||||||
"required": ["project_id", "card_id"]
|
"required": ["project_id", "card_id"]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "uncomplete_card",
|
||||||
|
"description": "Mark a card as incomplete",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
|
"card_id": {"type": "string", "description": "The card ID"}
|
||||||
|
},
|
||||||
|
"required": ["project_id", "card_id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get_card_steps",
|
||||||
|
"description": "Get all steps (sub-tasks) for a card",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
|
"card_id": {"type": "string", "description": "The card ID"}
|
||||||
|
},
|
||||||
|
"required": ["project_id", "card_id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "create_card_step",
|
||||||
|
"description": "Create a new step (sub-task) for a card",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
|
"card_id": {"type": "string", "description": "The card ID"},
|
||||||
|
"title": {"type": "string", "description": "The step title"},
|
||||||
|
"due_on": {"type": "string", "description": "Optional due date (ISO 8601 format)"},
|
||||||
|
"assignee_ids": {"type": "array", "items": {"type": "string"}, "description": "Array of person IDs to assign to the step"}
|
||||||
|
},
|
||||||
|
"required": ["project_id", "card_id", "title"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get_card_step",
|
||||||
|
"description": "Get details for a specific card step",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
|
"step_id": {"type": "string", "description": "The step ID"}
|
||||||
|
},
|
||||||
|
"required": ["project_id", "step_id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "update_card_step",
|
||||||
|
"description": "Update a card step",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
|
"step_id": {"type": "string", "description": "The step ID"},
|
||||||
|
"title": {"type": "string", "description": "The step title"},
|
||||||
|
"due_on": {"type": "string", "description": "Due date (ISO 8601 format)"},
|
||||||
|
"assignee_ids": {"type": "array", "items": {"type": "string"}, "description": "Array of person IDs to assign to the step"}
|
||||||
|
},
|
||||||
|
"required": ["project_id", "step_id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "delete_card_step",
|
||||||
|
"description": "Delete a card step",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
|
"step_id": {"type": "string", "description": "The step ID"}
|
||||||
|
},
|
||||||
|
"required": ["project_id", "step_id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "complete_card_step",
|
||||||
|
"description": "Mark a card step as complete",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
|
"step_id": {"type": "string", "description": "The step ID"}
|
||||||
|
},
|
||||||
|
"required": ["project_id", "step_id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "uncomplete_card_step",
|
||||||
|
"description": "Mark a card step as incomplete",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_id": {"type": "string", "description": "The project ID"},
|
||||||
|
"step_id": {"type": "string", "description": "The step ID"}
|
||||||
|
},
|
||||||
|
"required": ["project_id", "step_id"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -401,7 +529,7 @@ class MCPServer:
|
|||||||
logger.error(f"Error creating Basecamp client: {e}")
|
logger.error(f"Error creating Basecamp client: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
def handle_request(self, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||||
"""Handle an MCP request."""
|
"""Handle an MCP request."""
|
||||||
method = request.get("method")
|
method = request.get("method")
|
||||||
# Normalize method name for cursor compatibility
|
# Normalize method name for cursor compatibility
|
||||||
@@ -613,7 +741,9 @@ class MCPServer:
|
|||||||
}
|
}
|
||||||
elif tool_name == "get_daily_check_ins":
|
elif tool_name == "get_daily_check_ins":
|
||||||
project_id = arguments.get("project_id")
|
project_id = arguments.get("project_id")
|
||||||
page = arguments.get("page")
|
page = arguments.get("page", 1)
|
||||||
|
if page is not None and not isinstance(page, int):
|
||||||
|
page = 1
|
||||||
answers = client.get_daily_check_ins(project_id, page=page)
|
answers = client.get_daily_check_ins(project_id, page=page)
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
@@ -623,7 +753,9 @@ class MCPServer:
|
|||||||
elif tool_name == "get_question_answers":
|
elif tool_name == "get_question_answers":
|
||||||
project_id = arguments.get("project_id")
|
project_id = arguments.get("project_id")
|
||||||
question_id = arguments.get("question_id")
|
question_id = arguments.get("question_id")
|
||||||
page = arguments.get("page")
|
page = arguments.get("page", 1)
|
||||||
|
if page is not None and not isinstance(page, int):
|
||||||
|
page = 1
|
||||||
answers = client.get_question_answers(project_id, question_id, page=page)
|
answers = client.get_question_answers(project_id, question_id, page=page)
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
@@ -632,23 +764,43 @@ class MCPServer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Card Table tools implementation
|
# Card Table tools implementation
|
||||||
|
elif tool_name == "get_card_tables":
|
||||||
|
project_id = arguments.get("project_id")
|
||||||
|
card_tables = client.get_card_tables(project_id)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"card_tables": card_tables,
|
||||||
|
"count": len(card_tables)
|
||||||
|
}
|
||||||
|
|
||||||
elif tool_name == "get_card_table":
|
elif tool_name == "get_card_table":
|
||||||
project_id = arguments.get("project_id")
|
project_id = arguments.get("project_id")
|
||||||
try:
|
try:
|
||||||
card_table = client.get_card_table(project_id)
|
# First, let's see what card tables we find
|
||||||
# Also get the full details
|
card_tables = client.get_card_tables(project_id)
|
||||||
|
if not card_tables:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": "No card tables found in project",
|
||||||
|
"debug": f"Found {len(card_tables)} card tables"
|
||||||
|
}
|
||||||
|
|
||||||
|
card_table = card_tables[0] # Get the first card table
|
||||||
|
|
||||||
|
# Get the full details
|
||||||
card_table_details = client.get_card_table_details(project_id, card_table['id'])
|
card_table_details = client.get_card_table_details(project_id, card_table['id'])
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"card_table": card_table_details
|
"card_table": card_table_details,
|
||||||
|
"debug": f"Found {len(card_tables)} card tables, using first one with ID {card_table['id']}"
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if "Failed to get card table" in str(e):
|
error_msg = str(e)
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": "No card table found for this project. Make sure the Card Table tool is enabled in the project settings."
|
"message": f"Error getting card table: {error_msg}",
|
||||||
}
|
"debug": error_msg
|
||||||
raise
|
}
|
||||||
|
|
||||||
elif tool_name == "get_columns":
|
elif tool_name == "get_columns":
|
||||||
project_id = arguments.get("project_id")
|
project_id = arguments.get("project_id")
|
||||||
@@ -695,7 +847,10 @@ class MCPServer:
|
|||||||
project_id = arguments.get("project_id")
|
project_id = arguments.get("project_id")
|
||||||
column_id = arguments.get("column_id")
|
column_id = arguments.get("column_id")
|
||||||
position = arguments.get("position")
|
position = arguments.get("position")
|
||||||
client.move_column(project_id, column_id, position)
|
# Get the card table ID from the project
|
||||||
|
card_table = client.get_card_table(project_id)
|
||||||
|
card_table_id = card_table['id']
|
||||||
|
client.move_column(project_id, column_id, position, card_table_id)
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"message": f"Column moved to position {position}"
|
"message": f"Column moved to position {position}"
|
||||||
@@ -772,7 +927,9 @@ class MCPServer:
|
|||||||
column_id = arguments.get("column_id")
|
column_id = arguments.get("column_id")
|
||||||
title = arguments.get("title")
|
title = arguments.get("title")
|
||||||
content = arguments.get("content")
|
content = arguments.get("content")
|
||||||
card = client.create_card(project_id, column_id, title, content)
|
due_on = arguments.get("due_on")
|
||||||
|
notify = bool(arguments.get("notify", False))
|
||||||
|
card = client.create_card(project_id, column_id, title, content, due_on, notify)
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"card": card,
|
"card": card,
|
||||||
@@ -784,7 +941,9 @@ class MCPServer:
|
|||||||
card_id = arguments.get("card_id")
|
card_id = arguments.get("card_id")
|
||||||
title = arguments.get("title")
|
title = arguments.get("title")
|
||||||
content = arguments.get("content")
|
content = arguments.get("content")
|
||||||
card = client.update_card(project_id, card_id, title, content)
|
due_on = arguments.get("due_on")
|
||||||
|
assignee_ids = arguments.get("assignee_ids")
|
||||||
|
card = client.update_card(project_id, card_id, title, content, due_on, assignee_ids)
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"card": card,
|
"card": card,
|
||||||
@@ -795,20 +954,105 @@ class MCPServer:
|
|||||||
project_id = arguments.get("project_id")
|
project_id = arguments.get("project_id")
|
||||||
card_id = arguments.get("card_id")
|
card_id = arguments.get("card_id")
|
||||||
column_id = arguments.get("column_id")
|
column_id = arguments.get("column_id")
|
||||||
position = arguments.get("position")
|
client.move_card(project_id, card_id, column_id)
|
||||||
client.move_card(project_id, card_id, column_id, position)
|
|
||||||
message = "Card moved"
|
message = "Card moved"
|
||||||
if column_id and position:
|
if column_id:
|
||||||
message = f"Card moved to column {column_id} at position {position}"
|
|
||||||
elif column_id:
|
|
||||||
message = f"Card moved to column {column_id}"
|
message = f"Card moved to column {column_id}"
|
||||||
elif position:
|
|
||||||
message = f"Card moved to position {position}"
|
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"message": message
|
"message": message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elif tool_name == "complete_card":
|
||||||
|
project_id = arguments.get("project_id")
|
||||||
|
card_id = arguments.get("card_id")
|
||||||
|
client.complete_card(project_id, card_id)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "Card marked as complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
elif tool_name == "uncomplete_card":
|
||||||
|
project_id = arguments.get("project_id")
|
||||||
|
card_id = arguments.get("card_id")
|
||||||
|
client.uncomplete_card(project_id, card_id)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "Card marked as incomplete"
|
||||||
|
}
|
||||||
|
|
||||||
|
elif tool_name == "get_card_steps":
|
||||||
|
project_id = arguments.get("project_id")
|
||||||
|
card_id = arguments.get("card_id")
|
||||||
|
steps = client.get_card_steps(project_id, card_id)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"steps": steps,
|
||||||
|
"count": len(steps)
|
||||||
|
}
|
||||||
|
|
||||||
|
elif tool_name == "create_card_step":
|
||||||
|
project_id = arguments.get("project_id")
|
||||||
|
card_id = arguments.get("card_id")
|
||||||
|
title = arguments.get("title")
|
||||||
|
due_on = arguments.get("due_on")
|
||||||
|
assignee_ids = arguments.get("assignee_ids")
|
||||||
|
step = client.create_card_step(project_id, card_id, title, due_on, assignee_ids)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"step": step,
|
||||||
|
"message": f"Step '{title}' created successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
elif tool_name == "get_card_step":
|
||||||
|
project_id = arguments.get("project_id")
|
||||||
|
step_id = arguments.get("step_id")
|
||||||
|
step = client.get_card_step(project_id, step_id)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"step": step
|
||||||
|
}
|
||||||
|
|
||||||
|
elif tool_name == "update_card_step":
|
||||||
|
project_id = arguments.get("project_id")
|
||||||
|
step_id = arguments.get("step_id")
|
||||||
|
title = arguments.get("title")
|
||||||
|
due_on = arguments.get("due_on")
|
||||||
|
assignee_ids = arguments.get("assignee_ids")
|
||||||
|
step = client.update_card_step(project_id, step_id, title, due_on, assignee_ids)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"step": step,
|
||||||
|
"message": f"Step '{title}' updated successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
elif tool_name == "delete_card_step":
|
||||||
|
project_id = arguments.get("project_id")
|
||||||
|
step_id = arguments.get("step_id")
|
||||||
|
client.delete_card_step(project_id, step_id)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "Step deleted successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
elif tool_name == "complete_card_step":
|
||||||
|
project_id = arguments.get("project_id")
|
||||||
|
step_id = arguments.get("step_id")
|
||||||
|
client.complete_card_step(project_id, step_id)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "Step marked as complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
elif tool_name == "uncomplete_card_step":
|
||||||
|
project_id = arguments.get("project_id")
|
||||||
|
step_id = arguments.get("step_id")
|
||||||
|
client.uncomplete_card_step(project_id, step_id)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "Step marked as incomplete"
|
||||||
|
}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
"error": "Unknown tool",
|
"error": "Unknown tool",
|
||||||
|
|||||||
Reference in New Issue
Block a user