Fix token_storage function name and ensure Composio integration is fully functional
This commit is contained in:
420
composio_integration.py
Normal file
420
composio_integration.py
Normal file
@@ -0,0 +1,420 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from flask import Flask, request, jsonify
|
||||
from basecamp_client import BasecampClient
|
||||
from search_utils import BasecampSearch
|
||||
import token_storage
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
def get_basecamp_client(auth_mode='oauth'):
|
||||
"""
|
||||
Returns a BasecampClient instance with appropriate authentication.
|
||||
|
||||
Args:
|
||||
auth_mode (str): The authentication mode to use ('oauth' or 'basic')
|
||||
|
||||
Returns:
|
||||
BasecampClient: A client for interacting with Basecamp
|
||||
"""
|
||||
if auth_mode == 'oauth':
|
||||
# Get the OAuth token
|
||||
token_data = token_storage.get_token()
|
||||
if not token_data or 'access_token' not in token_data:
|
||||
logger.error("No OAuth token available")
|
||||
return None
|
||||
|
||||
# Create a client using the OAuth token
|
||||
account_id = os.getenv('BASECAMP_ACCOUNT_ID')
|
||||
user_agent = os.getenv('USER_AGENT')
|
||||
client = BasecampClient(
|
||||
account_id=account_id,
|
||||
user_agent=user_agent,
|
||||
access_token=token_data['access_token'],
|
||||
auth_mode='oauth'
|
||||
)
|
||||
return client
|
||||
else:
|
||||
# Basic auth is not recommended but keeping for compatibility
|
||||
username = os.getenv('BASECAMP_USERNAME')
|
||||
password = os.getenv('BASECAMP_PASSWORD')
|
||||
account_id = os.getenv('BASECAMP_ACCOUNT_ID')
|
||||
user_agent = os.getenv('USER_AGENT')
|
||||
|
||||
client = BasecampClient(
|
||||
username=username,
|
||||
password=password,
|
||||
account_id=account_id,
|
||||
user_agent=user_agent,
|
||||
auth_mode='basic'
|
||||
)
|
||||
return client
|
||||
|
||||
def get_schema():
|
||||
"""
|
||||
Returns the schema for Basecamp tools compatible with Composio's MCP format.
|
||||
|
||||
Returns:
|
||||
dict: A schema describing available tools and their parameters according to Composio specs
|
||||
"""
|
||||
schema = {
|
||||
"name": "Basecamp MCP Server",
|
||||
"description": "Integration with Basecamp 3 for project management and team collaboration",
|
||||
"version": "1.0.0",
|
||||
"auth": {
|
||||
"type": "oauth2",
|
||||
"redirect_url": "http://localhost:8000",
|
||||
"token_url": "http://localhost:8000/token/info"
|
||||
},
|
||||
"contact": {
|
||||
"name": "Basecamp MCP Server Team",
|
||||
"url": "https://github.com/georgeantonopoulos/Basecamp-MCP-Server"
|
||||
},
|
||||
"tools": [
|
||||
{
|
||||
"name": "GET_PROJECTS",
|
||||
"description": "Get all projects from Basecamp",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GET_PROJECT",
|
||||
"description": "Get details for a specific project",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "The ID of the project"}
|
||||
},
|
||||
"required": ["project_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GET_TODOLISTS",
|
||||
"description": "Get all todo lists for a project",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "The ID of the project"}
|
||||
},
|
||||
"required": ["project_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GET_TODOS",
|
||||
"description": "Get all todos for a specific todolist",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"todolist_id": {"type": "string", "description": "The ID of the todolist"}
|
||||
},
|
||||
"required": ["todolist_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GET_CAMPFIRE",
|
||||
"description": "Get all chat rooms (campfires) for a project",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "The ID of the project"}
|
||||
},
|
||||
"required": ["project_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GET_CAMPFIRE_LINES",
|
||||
"description": "Get messages from a specific chat room",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "The ID of the project"},
|
||||
"campfire_id": {"type": "string", "description": "The ID of the campfire/chat room"}
|
||||
},
|
||||
"required": ["project_id", "campfire_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SEARCH",
|
||||
"description": "Search across Basecamp resources",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "The search query"},
|
||||
"project_id": {"type": "string", "description": "Optional project ID to limit search scope"}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GET_COMMENTS",
|
||||
"description": "Get comments for a specific Basecamp object",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recording_id": {"type": "string", "description": "The ID of the object to get comments for"},
|
||||
"bucket_id": {"type": "string", "description": "The bucket ID"}
|
||||
},
|
||||
"required": ["recording_id", "bucket_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CREATE_COMMENT",
|
||||
"description": "Create a new comment on a Basecamp object",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recording_id": {"type": "string", "description": "The ID of the object to comment on"},
|
||||
"bucket_id": {"type": "string", "description": "The bucket ID"},
|
||||
"content": {"type": "string", "description": "The comment content"}
|
||||
},
|
||||
"required": ["recording_id", "bucket_id", "content"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
return schema
|
||||
|
||||
def handle_composio_request(data):
|
||||
"""
|
||||
Handle a request from Composio following MCP standards.
|
||||
|
||||
Args:
|
||||
data (dict): The request data containing tool name and parameters
|
||||
|
||||
Returns:
|
||||
dict: The result of the tool execution in MCP-compliant format
|
||||
"""
|
||||
# Check if the API key is valid (if provided)
|
||||
composio_api_key = os.getenv('COMPOSIO_API_KEY')
|
||||
request_api_key = request.headers.get('X-Composio-API-Key')
|
||||
|
||||
if composio_api_key and request_api_key and composio_api_key != request_api_key:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "authentication_error",
|
||||
"message": "Invalid API key provided"
|
||||
}
|
||||
}
|
||||
|
||||
tool_name = data.get('tool')
|
||||
params = data.get('params', {})
|
||||
|
||||
# Get a Basecamp client
|
||||
client = get_basecamp_client(auth_mode='oauth')
|
||||
if not client:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "authentication_required",
|
||||
"message": "OAuth authentication required",
|
||||
"auth_url": "http://localhost:8000/"
|
||||
}
|
||||
}
|
||||
|
||||
# Route to the appropriate handler based on tool_name
|
||||
try:
|
||||
if tool_name == "GET_PROJECTS":
|
||||
result = client.get_projects()
|
||||
return {
|
||||
"status": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
elif tool_name == "GET_PROJECT":
|
||||
project_id = params.get('project_id')
|
||||
if not project_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "invalid_parameters",
|
||||
"message": "Missing required parameter: project_id"
|
||||
}
|
||||
}
|
||||
result = client.get_project(project_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
elif tool_name == "GET_TODOLISTS":
|
||||
project_id = params.get('project_id')
|
||||
if not project_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "invalid_parameters",
|
||||
"message": "Missing required parameter: project_id"
|
||||
}
|
||||
}
|
||||
result = client.get_todolists(project_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
elif tool_name == "GET_TODOS":
|
||||
todolist_id = params.get('todolist_id')
|
||||
if not todolist_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "invalid_parameters",
|
||||
"message": "Missing required parameter: todolist_id"
|
||||
}
|
||||
}
|
||||
result = client.get_todos(todolist_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
elif tool_name == "GET_CAMPFIRE":
|
||||
project_id = params.get('project_id')
|
||||
if not project_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "invalid_parameters",
|
||||
"message": "Missing required parameter: project_id"
|
||||
}
|
||||
}
|
||||
result = client.get_campfires(project_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"data": {
|
||||
"campfire": result
|
||||
}
|
||||
}
|
||||
|
||||
elif tool_name == "GET_CAMPFIRE_LINES":
|
||||
project_id = params.get('project_id')
|
||||
campfire_id = params.get('campfire_id')
|
||||
if not project_id or not campfire_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "invalid_parameters",
|
||||
"message": "Missing required parameters: project_id and/or campfire_id"
|
||||
}
|
||||
}
|
||||
result = client.get_campfire_lines(project_id, campfire_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
elif tool_name == "SEARCH":
|
||||
query = params.get('query')
|
||||
project_id = params.get('project_id')
|
||||
if not query:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "invalid_parameters",
|
||||
"message": "Missing required parameter: query"
|
||||
}
|
||||
}
|
||||
|
||||
search = BasecampSearch(client=client)
|
||||
results = []
|
||||
|
||||
# Search projects
|
||||
if not project_id:
|
||||
projects = search.search_projects(query)
|
||||
if projects:
|
||||
results.extend([{"type": "project", "data": p} for p in projects])
|
||||
|
||||
# If project_id is provided, search within that project
|
||||
if project_id:
|
||||
# Search todolists
|
||||
todolists = search.search_todolists(query, project_id)
|
||||
if todolists:
|
||||
results.extend([{"type": "todolist", "data": t} for t in todolists])
|
||||
|
||||
# Search todos
|
||||
todos = search.search_todos(query, project_id)
|
||||
if todos:
|
||||
results.extend([{"type": "todo", "data": t} for t in todos])
|
||||
|
||||
# Search campfire lines
|
||||
campfires = client.get_campfires(project_id)
|
||||
for campfire in campfires:
|
||||
campfire_id = campfire.get('id')
|
||||
lines = search.search_campfire_lines(query, project_id, campfire_id)
|
||||
if lines:
|
||||
results.extend([{"type": "campfire_line", "data": l} for l in lines])
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"data": {
|
||||
"results": results,
|
||||
"count": len(results)
|
||||
}
|
||||
}
|
||||
|
||||
elif tool_name == "GET_COMMENTS":
|
||||
recording_id = params.get('recording_id')
|
||||
bucket_id = params.get('bucket_id')
|
||||
if not recording_id or not bucket_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "invalid_parameters",
|
||||
"message": "Missing required parameters: recording_id and/or bucket_id"
|
||||
}
|
||||
}
|
||||
result = client.get_comments(recording_id, bucket_id)
|
||||
return {
|
||||
"status": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
elif tool_name == "CREATE_COMMENT":
|
||||
recording_id = params.get('recording_id')
|
||||
bucket_id = params.get('bucket_id')
|
||||
content = params.get('content')
|
||||
if not recording_id or not bucket_id or not content:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "invalid_parameters",
|
||||
"message": "Missing required parameters"
|
||||
}
|
||||
}
|
||||
result = client.create_comment(recording_id, bucket_id, content)
|
||||
return {
|
||||
"status": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
else:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "unknown_tool",
|
||||
"message": f"Unknown tool: {tool_name}"
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling tool {tool_name}: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "server_error",
|
||||
"message": f"Error executing tool: {str(e)}"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user