diff --git a/.env b/.env new file mode 100644 index 0000000..db1e020 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +PLANKA_URL=https://planka.danilkolesnikov.ru +PLANKA_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImVjOGJiZTJjLThlY2ItNDhhYy1hOGQ4LTNkNjQxMWIwZDkyNSJ9.eyJpYXQiOjE3NzUyMzE5OTcsImV4cCI6MTgwNjc2Nzk5Nywic3ViIjoiMTc0NTE2NzY1NDM3NDYwNTgyNyJ9.wk7nNv807g6VDVQ242j9BtXTuUwIo4Of6Giqinf-KqQ diff --git a/server.py b/server.py index ec87013..6d33030 100644 --- a/server.py +++ b/server.py @@ -22,12 +22,22 @@ class PlankaClient: 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() + if not response.text: + return None + try: + return response.json() + except requests.exceptions.JSONDecodeError: + return None 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() + if not response.text: + return None + try: + return response.json() + except requests.exceptions.JSONDecodeError: + return None def get_projects(self) -> List[dict]: data = self._get("projects") @@ -37,12 +47,27 @@ class PlankaClient: def get_boards(self, project_id: str) -> List[dict]: data = self._get(f"projects/{project_id}") + boards = [] if isinstance(data, dict): if "boards" in data: - return data["boards"] + boards.extend(data["boards"]) + if "item" in data and isinstance(data["item"], dict) and "boards" in data["item"]: + boards.extend(data["item"]["boards"]) if "included" in data and isinstance(data["included"], dict) and "boards" in data["included"]: - return data["included"]["boards"] - return [] + boards.extend(data["included"]["boards"]) + return boards + + def get_board_lists(self, board_id: str) -> List[dict]: + data = self._get(f"boards/{board_id}") + lists = [] + if isinstance(data, dict): + if "lists" in data: + lists.extend(data["lists"]) + if "item" in data and isinstance(data["item"], dict) and "lists" in data["item"]: + lists.extend(data["item"]["lists"]) + if "included" in data and isinstance(data["included"], dict) and "lists" in data["included"]: + lists.extend(data["included"]["lists"]) + return lists def get_cards(self, board_id: str) -> List[dict]: data = self._get(f"boards/{board_id}/cards") @@ -59,7 +84,6 @@ class PlankaClient: }) def get_actions(self, card_id: str) -> List[dict]: - # Planka comments are in the 'actions' endpoint for a card data = self._get(f"cards/{card_id}/actions") if isinstance(data, dict) and "items" in data: return data["items"] @@ -100,6 +124,14 @@ def list_boards(project_id: str) -> str: return "No boards found." return "\n".join([f"- {b['name']} (ID: {b['id']})" for b in boards]) +@mcp.tool() +def list_board_columns(board_id: str) -> str: + """List all columns (lists) for a given board ID.""" + lists = get_client().get_board_lists(board_id) + if not lists: + return "No columns found." + return "\n".join([f"- {l['name']} (ID: {l['id']})" for l in lists]) + @mcp.tool() def list_cards(board_id: str) -> str: """List all cards for a given board ID.""" @@ -130,4 +162,17 @@ def add_comment(card_id: str, text: str) -> str: return f"Comment added to card {card_id}." if __name__ == "__main__": - mcp.run() + if len(sys.argv) > 1: + # CLI debug mode + cmd = sys.argv[1] + c = get_client() + if cmd == "list_projects": + print(list_projects()) + elif cmd == "list_boards": + print(list_boards(sys.argv[2])) + elif cmd == "list_cards": + print(list_cards(sys.argv[2])) + elif cmd == "list_columns": + print(list_board_columns(sys.argv[2])) + else: + mcp.run()