Merge pull request #4 from iamanikeev/fix-basecamp-api-issues
Add daily check-ins functionality, fix some issues with getting todo lists/sets
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import os
|
||||
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
||||
class BasecampClient:
|
||||
"""
|
||||
Client for interacting with Basecamp 3 API using Basic Authentication or OAuth 2.0.
|
||||
@@ -106,22 +108,22 @@ class BasecampClient:
|
||||
raise Exception(f"Failed to get project: {response.status_code} - {response.text}")
|
||||
|
||||
# To-do list methods
|
||||
def get_todosets(self, project_id):
|
||||
def get_todoset(self, project_id):
|
||||
"""Get the todoset for a project (Basecamp 3 has one todoset per project)."""
|
||||
response = self.get(f'projects/{project_id}/todoset.json')
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to get todoset: {response.status_code} - {response.text}")
|
||||
project = self.get_project(project_id)
|
||||
try:
|
||||
return next(_ for _ in project["dock"] if _["name"] == "todoset")
|
||||
except (IndexError, TypeError, StopIteration):
|
||||
raise Exception(f"Failed to get todoset for project: {project.id}. Project response: {project}")
|
||||
|
||||
def get_todolists(self, project_id):
|
||||
"""Get all todolists for a project."""
|
||||
# First get the todoset ID for this project
|
||||
todoset = self.get_todosets(project_id)
|
||||
todoset = self.get_todoset(project_id)
|
||||
todoset_id = todoset['id']
|
||||
|
||||
# Then get all todolists in this todoset
|
||||
response = self.get(f'todolists.json', {'todoset_id': todoset_id})
|
||||
response = self.get(f'buckets/{project_id}/todosets/{todoset_id}/todolists.json')
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
@@ -136,9 +138,9 @@ class BasecampClient:
|
||||
raise Exception(f"Failed to get todolist: {response.status_code} - {response.text}")
|
||||
|
||||
# To-do methods
|
||||
def get_todos(self, todolist_id):
|
||||
def get_todos(self, project_id, todolist_id):
|
||||
"""Get all todos in a todolist."""
|
||||
response = self.get(f'todolists/{todolist_id}/todos.json')
|
||||
response = self.get(f'buckets/{project_id}/todolists/{todolist_id}/todos.json')
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
@@ -233,22 +235,18 @@ class BasecampClient:
|
||||
raise Exception(f"Failed to get schedule: {str(e)}")
|
||||
|
||||
# Comments methods
|
||||
def get_comments(self, recording_id, bucket_id=None):
|
||||
def get_comments(self, project_id, recording_id):
|
||||
"""
|
||||
Get all comments for a recording (todo, message, etc.).
|
||||
Get all comments for a recording (todos, message, etc.).
|
||||
|
||||
Args:
|
||||
recording_id (int): ID of the recording (todo, message, etc.)
|
||||
bucket_id (int, optional): Project/bucket ID. If not provided, it will be extracted from the recording ID.
|
||||
recording_id (int): ID of the recording (todos, message, etc.)
|
||||
project_id (int): Project/bucket ID. If not provided, it will be extracted from the recording ID.
|
||||
|
||||
Returns:
|
||||
list: Comments for the recording
|
||||
"""
|
||||
if bucket_id is None:
|
||||
# Try to get the recording first to extract the bucket_id
|
||||
raise ValueError("bucket_id is required")
|
||||
|
||||
endpoint = f"buckets/{bucket_id}/recordings/{recording_id}/comments.json"
|
||||
endpoint = f"buckets/{project_id}/recordings/{recording_id}/comments.json"
|
||||
response = self.get(endpoint)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
@@ -329,4 +327,20 @@ class BasecampClient:
|
||||
if response.status_code == 204:
|
||||
return True
|
||||
else:
|
||||
raise Exception(f"Failed to delete comment: {response.status_code} - {response.text}")
|
||||
raise Exception(f"Failed to delete comment: {response.status_code} - {response.text}")
|
||||
|
||||
def get_daily_check_ins(self, project_id, page=1):
|
||||
project = self.get_project(project_id)
|
||||
questionnaire = next(_ for _ in project["dock"] if _["name"] == "questionnaire")
|
||||
endpoint = f"buckets/{project_id}/questionnaires/{questionnaire['id']}/questions.json"
|
||||
response = self.get(endpoint, params={"page": page})
|
||||
if response.status_code != 200:
|
||||
raise Exception("Failed to read questions")
|
||||
return response.json()
|
||||
|
||||
def get_question_answers(self, project_id, question_id, page=1):
|
||||
endpoint = f"buckets/{project_id}/questions/{question_id}/answers.json"
|
||||
response = self.get(endpoint, params={"page": page})
|
||||
if response.status_code != 200:
|
||||
raise Exception("Failed to read question answers")
|
||||
return response.json()
|
||||
|
||||
@@ -82,9 +82,10 @@ class MCPServer:
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"todolist_id": {"type": "string", "description": "The todo list ID"}
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"todolist_id": {"type": "string", "description": "The todo list ID"},
|
||||
},
|
||||
"required": ["todolist_id"]
|
||||
"required": ["project_id", "todolist_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -106,9 +107,9 @@ class MCPServer:
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recording_id": {"type": "string", "description": "The item ID"},
|
||||
"bucket_id": {"type": "string", "description": "The bucket/project ID"}
|
||||
"project_id": {"type": "string", "description": "The project ID"}
|
||||
},
|
||||
"required": ["recording_id", "bucket_id"]
|
||||
"required": ["recording_id", "project_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -122,6 +123,31 @@ class MCPServer:
|
||||
},
|
||||
"required": ["project_id", "campfire_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_daily_check_ins",
|
||||
"description": "Get project's daily checking questionnaire",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "The project ID"},
|
||||
"page": {"type": "integer", "description": "Page number paginated response"}
|
||||
}
|
||||
},
|
||||
"required": ["project_id"]
|
||||
},
|
||||
{
|
||||
"name": "get_question_answers",
|
||||
"description": "Get answers on daily check-in question",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "The project ID"},
|
||||
"question_id": {"type": "string", "description": "The question ID"},
|
||||
"page": {"type": "integer", "description": "Page number paginated response"}
|
||||
}
|
||||
},
|
||||
"required": ["project_id", "question_id"]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -312,7 +338,8 @@ class MCPServer:
|
||||
|
||||
elif tool_name == "get_todos":
|
||||
todolist_id = arguments.get("todolist_id")
|
||||
todos = client.get_todos(todolist_id)
|
||||
project_id = arguments.get("project_id")
|
||||
todos = client.get_todos(project_id, todolist_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"todos": todos,
|
||||
@@ -344,8 +371,8 @@ class MCPServer:
|
||||
|
||||
elif tool_name == "get_comments":
|
||||
recording_id = arguments.get("recording_id")
|
||||
bucket_id = arguments.get("bucket_id")
|
||||
comments = client.get_comments(recording_id, bucket_id)
|
||||
project_id = arguments.get("project_id")
|
||||
comments = client.get_comments(project_id, recording_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"comments": comments,
|
||||
@@ -361,6 +388,25 @@ class MCPServer:
|
||||
"campfire_lines": lines,
|
||||
"count": len(lines)
|
||||
}
|
||||
elif tool_name == "get_daily_check_ins":
|
||||
project_id = arguments.get("project_id")
|
||||
page = arguments.get("page")
|
||||
answers = client.get_daily_check_ins(project_id, page=page)
|
||||
return {
|
||||
"status": "success",
|
||||
"campfire_lines": answers,
|
||||
"count": len(answers)
|
||||
}
|
||||
elif tool_name == "get_question_answers":
|
||||
project_id = arguments.get("project_id")
|
||||
question_id = arguments.get("question_id")
|
||||
page = arguments.get("page")
|
||||
answers = client.get_question_answers(project_id, question_id, page=page)
|
||||
return {
|
||||
"status": "success",
|
||||
"campfire_lines": answers,
|
||||
"count": len(answers)
|
||||
}
|
||||
|
||||
else:
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user