Add additional Basecamp API tools
This commit is contained in:
10
README.md
10
README.md
@@ -80,6 +80,16 @@ Once configured, you can use these tools in Cursor:
|
||||
- `get_campfire_lines` - Get recent messages from a Basecamp campfire
|
||||
- `get_daily_check_ins` - Get project's daily check-in questions
|
||||
- `get_question_answers` - Get answers to daily check-in questions
|
||||
- `create_attachment` - Upload a file as an attachment
|
||||
- `get_events` - Get events for a recording
|
||||
- `get_webhooks` - List webhooks for a project
|
||||
- `create_webhook` - Create a webhook
|
||||
- `delete_webhook` - Delete a webhook
|
||||
- `get_documents` - List documents in a vault
|
||||
- `get_document` - Get a single document
|
||||
- `create_document` - Create a document
|
||||
- `update_document` - Update a document
|
||||
- `trash_document` - Move a document to trash
|
||||
|
||||
### Card Table Tools
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ class BasecampClient:
|
||||
"""Get all card tables for a project."""
|
||||
project = self.get_project(project_id)
|
||||
try:
|
||||
return [_ for _ in project["dock"] if _["name"] == "kanban_board"]
|
||||
return [item for item in project["dock"] if item.get("name") in ("kanban_board", "card_table")]
|
||||
except (IndexError, TypeError):
|
||||
return []
|
||||
|
||||
@@ -603,3 +603,110 @@ class BasecampClient:
|
||||
return True
|
||||
else:
|
||||
raise Exception(f"Failed to uncomplete card step: {response.status_code} - {response.text}")
|
||||
|
||||
# New methods for additional Basecamp API functionality
|
||||
def create_attachment(self, file_path, name, content_type="application/octet-stream"):
|
||||
"""Upload an attachment and return the attachable sgid."""
|
||||
with open(file_path, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
headers = self.headers.copy()
|
||||
headers["Content-Type"] = content_type
|
||||
headers["Content-Length"] = str(len(data))
|
||||
|
||||
endpoint = f"attachments.json?name={name}"
|
||||
response = requests.post(f"{self.base_url}/{endpoint}", headers=headers, data=data)
|
||||
if response.status_code == 201:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to create attachment: {response.status_code} - {response.text}")
|
||||
|
||||
def get_events(self, project_id, recording_id):
|
||||
"""Get events for a recording."""
|
||||
endpoint = f"buckets/{project_id}/recordings/{recording_id}/events.json"
|
||||
response = self.get(endpoint)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to get events: {response.status_code} - {response.text}")
|
||||
|
||||
def get_webhooks(self, project_id):
|
||||
"""List webhooks for a project."""
|
||||
endpoint = f"buckets/{project_id}/webhooks.json"
|
||||
response = self.get(endpoint)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to get webhooks: {response.status_code} - {response.text}")
|
||||
|
||||
def create_webhook(self, project_id, payload_url, types=None):
|
||||
"""Create a webhook for a project."""
|
||||
data = {"payload_url": payload_url}
|
||||
if types:
|
||||
data["types"] = types
|
||||
endpoint = f"buckets/{project_id}/webhooks.json"
|
||||
response = self.post(endpoint, data)
|
||||
if response.status_code == 201:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to create webhook: {response.status_code} - {response.text}")
|
||||
|
||||
def delete_webhook(self, project_id, webhook_id):
|
||||
"""Delete a webhook."""
|
||||
endpoint = f"buckets/{project_id}/webhooks/{webhook_id}.json"
|
||||
response = self.delete(endpoint)
|
||||
if response.status_code == 204:
|
||||
return True
|
||||
else:
|
||||
raise Exception(f"Failed to delete webhook: {response.status_code} - {response.text}")
|
||||
|
||||
def get_documents(self, project_id, vault_id):
|
||||
"""List documents in a vault."""
|
||||
endpoint = f"buckets/{project_id}/vaults/{vault_id}/documents.json"
|
||||
response = self.get(endpoint)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to get documents: {response.status_code} - {response.text}")
|
||||
|
||||
def get_document(self, project_id, document_id):
|
||||
"""Get a single document."""
|
||||
endpoint = f"buckets/{project_id}/documents/{document_id}.json"
|
||||
response = self.get(endpoint)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to get document: {response.status_code} - {response.text}")
|
||||
|
||||
def create_document(self, project_id, vault_id, title, content, status="active"):
|
||||
"""Create a document in a vault."""
|
||||
data = {"title": title, "content": content, "status": status}
|
||||
endpoint = f"buckets/{project_id}/vaults/{vault_id}/documents.json"
|
||||
response = self.post(endpoint, data)
|
||||
if response.status_code == 201:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to create document: {response.status_code} - {response.text}")
|
||||
|
||||
def update_document(self, project_id, document_id, title=None, content=None):
|
||||
"""Update a document's title or content."""
|
||||
data = {}
|
||||
if title:
|
||||
data["title"] = title
|
||||
if content:
|
||||
data["content"] = content
|
||||
endpoint = f"buckets/{project_id}/documents/{document_id}.json"
|
||||
response = self.put(endpoint, data)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to update document: {response.status_code} - {response.text}")
|
||||
|
||||
def trash_document(self, project_id, document_id):
|
||||
"""Trash a document."""
|
||||
endpoint = f"buckets/{project_id}/recordings/{document_id}/status/trashed.json"
|
||||
response = self.put(endpoint)
|
||||
if response.status_code == 204:
|
||||
return True
|
||||
else:
|
||||
raise Exception(f"Failed to trash document: {response.status_code} - {response.text}")
|
||||
|
||||
@@ -490,6 +490,131 @@ class MCPServer:
|
||||
},
|
||||
"required": ["project_id", "step_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_attachment",
|
||||
"description": "Upload a file as an attachment",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {"type": "string", "description": "Local path to file"},
|
||||
"name": {"type": "string", "description": "Filename for Basecamp"},
|
||||
"content_type": {"type": "string", "description": "MIME type"}
|
||||
},
|
||||
"required": ["file_path", "name"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_events",
|
||||
"description": "Get events for a recording",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"recording_id": {"type": "string", "description": "Recording ID"}
|
||||
},
|
||||
"required": ["project_id", "recording_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_webhooks",
|
||||
"description": "List webhooks for a project",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"}
|
||||
},
|
||||
"required": ["project_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_webhook",
|
||||
"description": "Create a webhook",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"payload_url": {"type": "string", "description": "Payload URL"},
|
||||
"types": {"type": "array", "items": {"type": "string"}, "description": "Event types"}
|
||||
},
|
||||
"required": ["project_id", "payload_url"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "delete_webhook",
|
||||
"description": "Delete a webhook",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"webhook_id": {"type": "string", "description": "Webhook ID"}
|
||||
},
|
||||
"required": ["project_id", "webhook_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_documents",
|
||||
"description": "List documents in a vault",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"vault_id": {"type": "string", "description": "Vault ID"}
|
||||
},
|
||||
"required": ["project_id", "vault_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_document",
|
||||
"description": "Get a single document",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"document_id": {"type": "string", "description": "Document ID"}
|
||||
},
|
||||
"required": ["project_id", "document_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_document",
|
||||
"description": "Create a document in a vault",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"vault_id": {"type": "string", "description": "Vault ID"},
|
||||
"title": {"type": "string", "description": "Document title"},
|
||||
"content": {"type": "string", "description": "Document HTML content"}
|
||||
},
|
||||
"required": ["project_id", "vault_id", "title", "content"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "update_document",
|
||||
"description": "Update a document",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"document_id": {"type": "string", "description": "Document ID"},
|
||||
"title": {"type": "string", "description": "New title"},
|
||||
"content": {"type": "string", "description": "New HTML content"}
|
||||
},
|
||||
"required": ["project_id", "document_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "trash_document",
|
||||
"description": "Move a document to trash",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"document_id": {"type": "string", "description": "Document ID"}
|
||||
},
|
||||
"required": ["project_id", "document_id"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -777,23 +902,11 @@ class MCPServer:
|
||||
elif tool_name == "get_card_table":
|
||||
project_id = arguments.get("project_id")
|
||||
try:
|
||||
# First, let's see what card tables we find
|
||||
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 = client.get_card_table(project_id)
|
||||
card_table_details = client.get_card_table_details(project_id, card_table['id'])
|
||||
return {
|
||||
"status": "success",
|
||||
"card_table": card_table_details,
|
||||
"debug": f"Found {len(card_tables)} card tables, using first one with ID {card_table['id']}"
|
||||
"card_table": card_table_details
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
@@ -1052,6 +1165,104 @@ class MCPServer:
|
||||
"message": "Step marked as incomplete"
|
||||
}
|
||||
|
||||
elif tool_name == "create_attachment":
|
||||
file_path = arguments.get("file_path")
|
||||
name = arguments.get("name")
|
||||
content_type = arguments.get("content_type", "application/octet-stream")
|
||||
result = client.create_attachment(file_path, name, content_type)
|
||||
return {
|
||||
"status": "success",
|
||||
"attachment": result
|
||||
}
|
||||
|
||||
elif tool_name == "get_events":
|
||||
project_id = arguments.get("project_id")
|
||||
recording_id = arguments.get("recording_id")
|
||||
events = client.get_events(project_id, recording_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"events": events,
|
||||
"count": len(events)
|
||||
}
|
||||
|
||||
elif tool_name == "get_webhooks":
|
||||
project_id = arguments.get("project_id")
|
||||
hooks = client.get_webhooks(project_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"webhooks": hooks,
|
||||
"count": len(hooks)
|
||||
}
|
||||
|
||||
elif tool_name == "create_webhook":
|
||||
project_id = arguments.get("project_id")
|
||||
payload_url = arguments.get("payload_url")
|
||||
types = arguments.get("types")
|
||||
hook = client.create_webhook(project_id, payload_url, types)
|
||||
return {
|
||||
"status": "success",
|
||||
"webhook": hook
|
||||
}
|
||||
|
||||
elif tool_name == "delete_webhook":
|
||||
project_id = arguments.get("project_id")
|
||||
webhook_id = arguments.get("webhook_id")
|
||||
client.delete_webhook(project_id, webhook_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Webhook deleted"
|
||||
}
|
||||
|
||||
elif tool_name == "get_documents":
|
||||
project_id = arguments.get("project_id")
|
||||
vault_id = arguments.get("vault_id")
|
||||
docs = client.get_documents(project_id, vault_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"documents": docs,
|
||||
"count": len(docs)
|
||||
}
|
||||
|
||||
elif tool_name == "get_document":
|
||||
project_id = arguments.get("project_id")
|
||||
document_id = arguments.get("document_id")
|
||||
doc = client.get_document(project_id, document_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"document": doc
|
||||
}
|
||||
|
||||
elif tool_name == "create_document":
|
||||
project_id = arguments.get("project_id")
|
||||
vault_id = arguments.get("vault_id")
|
||||
title = arguments.get("title")
|
||||
content = arguments.get("content")
|
||||
doc = client.create_document(project_id, vault_id, title, content)
|
||||
return {
|
||||
"status": "success",
|
||||
"document": doc
|
||||
}
|
||||
|
||||
elif tool_name == "update_document":
|
||||
project_id = arguments.get("project_id")
|
||||
document_id = arguments.get("document_id")
|
||||
title = arguments.get("title")
|
||||
content = arguments.get("content")
|
||||
doc = client.update_document(project_id, document_id, title, content)
|
||||
return {
|
||||
"status": "success",
|
||||
"document": doc
|
||||
}
|
||||
|
||||
elif tool_name == "trash_document":
|
||||
project_id = arguments.get("project_id")
|
||||
document_id = arguments.get("document_id")
|
||||
client.trash_document(project_id, document_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Document trashed"
|
||||
}
|
||||
|
||||
else:
|
||||
return {
|
||||
"error": "Unknown tool",
|
||||
|
||||
Reference in New Issue
Block a user