# Directory Structure ``` ├── .env.example ├── .gitignore ├── .python-version ├── pyproject.toml ├── README.md ├── server.py └── uv.lock ``` # Files -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- ``` 3.11 ``` -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` MOCHI_API_KEY="" ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Python-generated files __pycache__/ *.py[oc] build/ dist/ wheels/ *.egg-info # Virtual environments .venv .env ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml [project] name = "mochi-cards" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.11" dependencies = [ "httpx>=0.28.1", "mcp[cli]>=1.5.0", ] ``` -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- ```python import os from typing import Any import httpx from mcp.server.fastmcp import FastMCP from dotenv import load_dotenv load_dotenv() mcp = FastMCP("mochi-cards") MOCHI_API_BASE = "https://app.mochi.cards/api" MOCHI_API_KEY = os.getenv("MOCHI_API_KEY") async def make_mochi_request( url: str, method: str = "GET", data: dict = None ) -> dict[str, Any] | None: """Make a request to the Mochi API with proper error handling.""" headers = {"Accept": "application/json", "Content-Type": "application/json"} auth = (MOCHI_API_KEY, "") if MOCHI_API_KEY else None async with httpx.AsyncClient() as client: try: if method == "GET": response = await client.get( url, headers=headers, auth=auth, timeout=30.0 ) elif method == "POST": response = await client.post( url, headers=headers, json=data, auth=auth, timeout=30.0 ) elif method == "DELETE": response = await client.delete( url, headers=headers, auth=auth, timeout=30.0 ) response.raise_for_status() return response.json() if response.content else None except Exception as e: return {"error": str(e)} @mcp.tool() async def mochi_list_decks(bookmark: str = None) -> str: """List all decks in your Mochi account. Args: api_key: Your Mochi API key bookmark: Optional bookmark for pagination """ url = f"{MOCHI_API_BASE}/decks" if bookmark: url += f"?bookmark={bookmark}" response = await make_mochi_request(url) if not response or "error" in response: return f"Error fetching decks: {response.get('error', 'Unknown error')}" decks = response.get("docs", []) result = "Your Mochi Decks:\n\n" for deck in decks: result += f"ID: {deck.get('id')}\nName: {deck.get('name')}\n\n" if response.get("bookmark"): result += f"\nMore decks available. Use bookmark: {response.get('bookmark')}" return result @mcp.tool() async def mochi_create_card(deck_id: str, content: str, tags: list = None) -> str: """Create a new card in a Mochi deck. Args: api_key: Your Mochi API key deck_id: The ID of the deck to add the card to content: Markdown content for the card tags: Optional list of tags to add to the card """ url = f"{MOCHI_API_BASE}/cards" data = {"content": content, "deck-id": deck_id} if tags: data["manual-tags"] = tags response = await make_mochi_request(url, method="POST", data=data) if not response or "error" in response: return f"Error creating card: {response.get('error', 'Unknown error')}" return f"Card created successfully with ID: {response.get('id')}" @mcp.tool() async def mochi_get_card(card_id: str) -> str: """Get details of a specific Mochi card. Args: api_key: Your Mochi API key card_id: The ID of the card to retrieve """ url = f"{MOCHI_API_BASE}/cards/{card_id}" response = await make_mochi_request(url) if not response or "error" in response: return f"Error fetching card: {response.get('error', 'Unknown error')}" result = f"Card ID: {response.get('id')}\n" result += f"Deck ID: {response.get('deck-id')}\n" result += f"Content: {response.get('content')}\n" result += f"Created: {response.get('created-at', {}).get('date')}\n" result += f"Updated: {response.get('updated-at', {}).get('date')}\n" if response.get("tags"): result += f"Tags: {', '.join(response.get('tags'))}\n" return result @mcp.tool() async def mochi_update_card( card_id: str, content: str = None, deck_id: str = None, archived: bool = None, tags: list = None, ) -> str: """Update an existing Mochi card. Args: api_key: Your Mochi API key card_id: The ID of the card to update content: Optional new markdown content deck_id: Optional new deck ID to move the card to archived: Optional boolean to archive/unarchive the card tags: Optional list of tags to replace existing tags """ url = f"{MOCHI_API_BASE}/cards/{card_id}" data = {} if content is not None: data["content"] = content if deck_id is not None: data["deck-id"] = deck_id if archived is not None: data["archived?"] = archived if tags is not None: data["manual-tags"] = tags response = await make_mochi_request( url, method="POST", data=data, ) if not response or "error" in response: return f"Error updating card: {response.get('error', 'Unknown error')}" return f"Card {card_id} updated successfully" @mcp.tool() async def mochi_delete_card(card_id: str) -> str: """Delete a Mochi card permanently. Args: api_key: Your Mochi API key card_id: The ID of the card to delete """ url = f"{MOCHI_API_BASE}/cards/{card_id}" response = await make_mochi_request(url, method="DELETE") if response and "error" in response: return f"Error deleting card: {response.get('error')}" return f"Card {card_id} deleted successfully" if __name__ == "__main__": mcp.run(transport="stdio") ```