feat(planka): add tools for comments (list_comments, add_comment)
This commit is contained in:
45
server.py
45
server.py
@@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
|
import sys
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
@@ -16,8 +17,6 @@ class PlankaClient:
|
|||||||
def __init__(self, url: str, token: str):
|
def __init__(self, url: str, token: str):
|
||||||
self.url = url.rstrip("/")
|
self.url = url.rstrip("/")
|
||||||
self.token = token
|
self.token = token
|
||||||
# Planka uses standard JWT tokens, but they don't always require "Bearer " in the header
|
|
||||||
# for all internal API calls, though standard is to use it.
|
|
||||||
self.headers = {"Authorization": f"Bearer {token}"}
|
self.headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
def _get(self, endpoint: str, params: Optional[dict] = None) -> Any:
|
def _get(self, endpoint: str, params: Optional[dict] = None) -> Any:
|
||||||
@@ -32,16 +31,18 @@ class PlankaClient:
|
|||||||
|
|
||||||
def get_projects(self) -> List[dict]:
|
def get_projects(self) -> List[dict]:
|
||||||
data = self._get("projects")
|
data = self._get("projects")
|
||||||
# Handle Planka API response structure which often wraps items in 'items'
|
|
||||||
if isinstance(data, dict) and "items" in data:
|
if isinstance(data, dict) and "items" in data:
|
||||||
return data["items"]
|
return data["items"]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_boards(self, project_id: str) -> List[dict]:
|
def get_boards(self, project_id: str) -> List[dict]:
|
||||||
data = self._get(f"projects/{project_id}/boards")
|
data = self._get(f"projects/{project_id}")
|
||||||
if isinstance(data, dict) and "items" in data:
|
if isinstance(data, dict):
|
||||||
return data["items"]
|
if "boards" in data:
|
||||||
return data
|
return data["boards"]
|
||||||
|
if "included" in data and isinstance(data["included"], dict) and "boards" in data["included"]:
|
||||||
|
return data["included"]["boards"]
|
||||||
|
return []
|
||||||
|
|
||||||
def get_cards(self, board_id: str) -> List[dict]:
|
def get_cards(self, board_id: str) -> List[dict]:
|
||||||
data = self._get(f"boards/{board_id}/cards")
|
data = self._get(f"boards/{board_id}/cards")
|
||||||
@@ -57,6 +58,21 @@ class PlankaClient:
|
|||||||
"description": description
|
"description": description
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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"]
|
||||||
|
return data
|
||||||
|
|
||||||
|
def create_comment(self, card_id: str, text: str) -> dict:
|
||||||
|
return self._post(f"cards/{card_id}/actions", {
|
||||||
|
"type": "commentCard",
|
||||||
|
"data": {
|
||||||
|
"text": text
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
# Initialize client lazily
|
# Initialize client lazily
|
||||||
client = None
|
client = None
|
||||||
|
|
||||||
@@ -98,5 +114,20 @@ def create_card(board_id: str, list_id: str, name: str, description: str = "") -
|
|||||||
card = get_client().create_card(board_id, list_id, name, description)
|
card = get_client().create_card(board_id, list_id, name, description)
|
||||||
return f"Created card: {card['name']} (ID: {card['id']})"
|
return f"Created card: {card['name']} (ID: {card['id']})"
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def list_comments(card_id: str) -> str:
|
||||||
|
"""List all comments for a given card ID."""
|
||||||
|
actions = get_client().get_actions(card_id)
|
||||||
|
comments = [a for a in actions if a.get('type') == 'commentCard']
|
||||||
|
if not comments:
|
||||||
|
return "No comments found."
|
||||||
|
return "\n".join([f"- {c['data']['text']} (By: {c['userId']}, at: {c['createdAt']})" for c in comments])
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def add_comment(card_id: str, text: str) -> str:
|
||||||
|
"""Add a comment to a card."""
|
||||||
|
action = get_client().create_comment(card_id, text)
|
||||||
|
return f"Comment added to card {card_id}."
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
mcp.run()
|
mcp.run()
|
||||||
|
|||||||
Reference in New Issue
Block a user