commit 154113f8a34b0c8befefc80e8fb0dd3808104eca Author: Flash Date: Fri Apr 3 14:58:40 2026 +0000 Add basic Planka MCP server with project, board, and card tools diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cd5df9a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +mcp +requests +python-dotenv diff --git a/server.py b/server.py new file mode 100644 index 0000000..9c83fcb --- /dev/null +++ b/server.py @@ -0,0 +1,84 @@ +import os +import requests +from typing import Any, List, Optional +from mcp.server.fastmcp import FastMCP +from dotenv import load_dotenv + +load_dotenv() + +PLANKA_URL = os.getenv("PLANKA_URL", "http://localhost:3000") +PLANKA_TOKEN = os.getenv("PLANKA_TOKEN") + +# Initialize FastMCP server +mcp = FastMCP("Planka") + +class PlankaClient: + def __init__(self, url: str, token: str): + self.url = url.rstrip("/") + self.token = token + self.headers = {"Authorization": f"Bearer {token}"} + + def _get(self, endpoint: str, params: Optional[dict] = None) -> Any: + response = requests.get(f"{self.url}/api/{endpoint}", headers=self.headers, params=params) + response.raise_for_status() + return response.json() + + def _post(self, endpoint: str, data: dict) -> Any: + response = requests.post(f"{self.url}/api/{endpoint}", headers=self.headers, json=data) + response.raise_for_status() + return response.json() + + def get_projects(self) -> List[dict]: + return self._get("projects") + + def get_boards(self, project_id: str) -> List[dict]: + return self._get(f"projects/{project_id}/boards") + + def get_cards(self, board_id: str) -> List[dict]: + return self._get(f"boards/{board_id}/cards") + + def create_card(self, board_id: str, list_id: str, name: str, description: str = "") -> dict: + return self._post("cards", { + "boardId": board_id, + "listId": list_id, + "name": name, + "description": description + }) + +# Initialize client lazily +client = None + +def get_client(): + global client + if client is None: + if not PLANKA_TOKEN: + raise ValueError("PLANKA_TOKEN is required") + client = PlankaClient(PLANKA_URL, PLANKA_TOKEN) + return client + +@mcp.tool() +def list_projects() -> str: + """List all Planka projects available to the user.""" + projects = get_client().get_projects() + return "\n".join([f"- {p['name']} (ID: {p['id']})" for p in projects]) + +@mcp.tool() +def list_boards(project_id: str) -> str: + """List all boards for a given project ID.""" + boards = get_client().get_boards(project_id) + return "\n".join([f"- {b['name']} (ID: {b['id']})" for b in boards]) + +@mcp.tool() +def list_cards(board_id: str) -> str: + """List all cards for a given board ID.""" + cards = get_client().get_cards(board_id) + return "\n".join([f"- {c['name']} (ID: {c['id']})" for c in cards]) + +@mcp.tool() +def create_card(board_id: str, list_id: str, name: str, description: str = "") -> str: + """Create a new card on a Planka board.""" + card = get_client().create_card(board_id, list_id, name, description) + return f"Created card: {card['name']} (ID: {card['id']})" + +if __name__ == "__main__": + mcp.run()