contest/orbit-boards
Orbit Boards, a private, remixable team task board built with FastAPI, DataK3, AppID, Models and MCP.
A remix template · 0 remixes · Remix contest/orbit-boards
app/boards/doors/mcp.py
"""Action-oriented MCP tools. Every operation uses the same authorized application service."""
from typing import Optional
from mcp.server.mcpserver import Context, MCPServer
from mcp.server.mcpserver.exceptions import ToolError
from boards.application.use_cases import Boards, NotFound
from boards.doors.identity import person_from
from boards.domain.model import Forbidden, Invalid
INSTRUCTIONS = (
"Orbit Boards is a private team task board. Start with my_boards to see only boards "
"available to the signed-in person. Lists are To do, Doing and Done. Use action tools "
"for changes; viewers can read but cannot modify."
)
def build_mcp(boards: Boards) -> MCPServer:
server = MCPServer(name="orbit-boards", instructions=INSTRUCTIONS)
def call(ctx: Context, fn, *args, **kwargs):
person = person_from(ctx.headers or {})
if person is None:
raise ToolError("401: sign in first")
try:
return fn(person, *args, **kwargs)
except (NotFound, Forbidden, Invalid) as error:
raise ToolError(str(error))
except ValueError as error:
raise ToolError("invalid value") from error
@server.tool()
def list_boards(ctx: Context) -> list:
"""List boards available to the signed-in person and their role on each."""
return call(ctx, boards.my_boards)
@server.tool()
def show_board(board_id: str, ctx: Context) -> dict:
"""Show one board and its cards, lists, labels, due dates and team."""
return call(ctx, boards.show_board, board_id)
@server.tool()
def list_my_cards(ctx: Context, days: int = 7) -> list:
"""List your assigned, unfinished cards due within the next number of days, including overdue cards."""
return call(ctx, boards.list_my_cards, days)
@server.tool()
def list_overdue(ctx: Context, board_id: Optional[str] = None) -> list:
"""List unfinished cards past their due date on visible boards, optionally limited to one board."""
return call(ctx, boards.list_overdue, board_id)
@server.tool()
def create_board(title: str, ctx: Context) -> dict:
"""Create a new private board. The caller becomes its owner."""
return call(ctx, boards.create_board, title)
@server.tool()
def add_card(board_id: str, title: str, ctx: Context, list_name: str = "To do",
description: str = "", assignee: Optional[str] = None,
due_date: Optional[str] = None, labels: Optional[list[str]] = None) -> dict:
"""Create a card on a visible board, optionally with description, assignee, due date and labels."""
return call(ctx, boards.add_card, board_id, title, list_name, description,
assignee, due_date, labels)
@server.tool()
def update_card(card_id: str, ctx: Context, title: Optional[str] = None,
description: Optional[str] = None, due_date: Optional[str] = None,
labels: Optional[list[str]] = None) -> dict:
"""Edit card title, description, due date or labels. Omit fields that should stay unchanged."""
changes = {key: value for key, value in {"title": title, "description": description,
"due_date": due_date, "labels": labels}.items() if value is not None}
return call(ctx, boards.update_card, card_id, **changes)
@server.tool()
def move_card(card_id: str, list_name: str, ctx: Context, position: Optional[int] = None) -> dict:
"""Move or reorder a card in the same board. Valid lists are To do, Doing and Done."""
return call(ctx, boards.move_card, card_id, list_name, position)
@server.tool()
def assign_card(card_id: str, email: Optional[str], ctx: Context) -> dict:
"""Assign a card to a board member by email, or clear its assignment with null."""
return call(ctx, boards.assign_card, card_id, email)
@server.tool()
def get_activity(board_id: str, ctx: Context, limit: int = 50) -> list[dict]:
"""Read recent card activity on a board the caller can see."""
return call(ctx, boards.get_activity, board_id, limit)
@server.tool()
def add_member(board_id: str, email: str, ctx: Context, role: str = "member") -> dict:
"""Owner only: add a person as member or read-only viewer to a board."""
return call(ctx, boards.add_member, board_id, email, role)
@server.tool()
def suggest_checklist(card_id: str, ctx: Context) -> dict:
"""Ask Dodil Models for a suggested checklist. This does not change the card."""
return call(ctx, boards.suggest_checklist, card_id)
return server