#
tokens: 2261/50000 6/6 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .env.example
├── .gitignore
├── .python-version
├── pyproject.toml
├── README.md
├── server.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
1 | 3.11
2 | 
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
1 | MOCHI_API_KEY=""
2 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python-generated files
 2 | __pycache__/
 3 | *.py[oc]
 4 | build/
 5 | dist/
 6 | wheels/
 7 | *.egg-info
 8 | 
 9 | # Virtual environments
10 | .venv
11 | .env
12 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
1 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "mochi-cards"
 3 | version = "0.1.0"
 4 | description = "Add your description here"
 5 | readme = "README.md"
 6 | requires-python = ">=3.11"
 7 | dependencies = [
 8 |     "httpx>=0.28.1",
 9 |     "mcp[cli]>=1.5.0",
10 | ]
11 | 
```

--------------------------------------------------------------------------------
/server.py:
--------------------------------------------------------------------------------

```python
  1 | import os
  2 | from typing import Any
  3 | import httpx
  4 | from mcp.server.fastmcp import FastMCP
  5 | 
  6 | from dotenv import load_dotenv
  7 | 
  8 | load_dotenv()
  9 | 
 10 | mcp = FastMCP("mochi-cards")
 11 | 
 12 | MOCHI_API_BASE = "https://app.mochi.cards/api"
 13 | MOCHI_API_KEY = os.getenv("MOCHI_API_KEY")
 14 | 
 15 | 
 16 | async def make_mochi_request(
 17 |     url: str, method: str = "GET", data: dict = None
 18 | ) -> dict[str, Any] | None:
 19 |     """Make a request to the Mochi API with proper error handling."""
 20 |     headers = {"Accept": "application/json", "Content-Type": "application/json"}
 21 |     auth = (MOCHI_API_KEY, "") if MOCHI_API_KEY else None
 22 | 
 23 |     async with httpx.AsyncClient() as client:
 24 |         try:
 25 |             if method == "GET":
 26 |                 response = await client.get(
 27 |                     url, headers=headers, auth=auth, timeout=30.0
 28 |                 )
 29 |             elif method == "POST":
 30 |                 response = await client.post(
 31 |                     url, headers=headers, json=data, auth=auth, timeout=30.0
 32 |                 )
 33 |             elif method == "DELETE":
 34 |                 response = await client.delete(
 35 |                     url, headers=headers, auth=auth, timeout=30.0
 36 |                 )
 37 | 
 38 |             response.raise_for_status()
 39 |             return response.json() if response.content else None
 40 |         except Exception as e:
 41 |             return {"error": str(e)}
 42 | 
 43 | 
 44 | @mcp.tool()
 45 | async def mochi_list_decks(bookmark: str = None) -> str:
 46 |     """List all decks in your Mochi account.
 47 | 
 48 |     Args:
 49 |         api_key: Your Mochi API key
 50 |         bookmark: Optional bookmark for pagination
 51 |     """
 52 |     url = f"{MOCHI_API_BASE}/decks"
 53 |     if bookmark:
 54 |         url += f"?bookmark={bookmark}"
 55 | 
 56 |     response = await make_mochi_request(url)
 57 | 
 58 |     if not response or "error" in response:
 59 |         return f"Error fetching decks: {response.get('error', 'Unknown error')}"
 60 | 
 61 |     decks = response.get("docs", [])
 62 |     result = "Your Mochi Decks:\n\n"
 63 | 
 64 |     for deck in decks:
 65 |         result += f"ID: {deck.get('id')}\nName: {deck.get('name')}\n\n"
 66 | 
 67 |     if response.get("bookmark"):
 68 |         result += f"\nMore decks available. Use bookmark: {response.get('bookmark')}"
 69 | 
 70 |     return result
 71 | 
 72 | 
 73 | @mcp.tool()
 74 | async def mochi_create_card(deck_id: str, content: str, tags: list = None) -> str:
 75 |     """Create a new card in a Mochi deck.
 76 | 
 77 |     Args:
 78 |         api_key: Your Mochi API key
 79 |         deck_id: The ID of the deck to add the card to
 80 |         content: Markdown content for the card
 81 |         tags: Optional list of tags to add to the card
 82 |     """
 83 |     url = f"{MOCHI_API_BASE}/cards"
 84 | 
 85 |     data = {"content": content, "deck-id": deck_id}
 86 | 
 87 |     if tags:
 88 |         data["manual-tags"] = tags
 89 | 
 90 |     response = await make_mochi_request(url, method="POST", data=data)
 91 | 
 92 |     if not response or "error" in response:
 93 |         return f"Error creating card: {response.get('error', 'Unknown error')}"
 94 | 
 95 |     return f"Card created successfully with ID: {response.get('id')}"
 96 | 
 97 | 
 98 | @mcp.tool()
 99 | async def mochi_get_card(card_id: str) -> str:
100 |     """Get details of a specific Mochi card.
101 | 
102 |     Args:
103 |         api_key: Your Mochi API key
104 |         card_id: The ID of the card to retrieve
105 |     """
106 |     url = f"{MOCHI_API_BASE}/cards/{card_id}"
107 | 
108 |     response = await make_mochi_request(url)
109 | 
110 |     if not response or "error" in response:
111 |         return f"Error fetching card: {response.get('error', 'Unknown error')}"
112 | 
113 |     result = f"Card ID: {response.get('id')}\n"
114 |     result += f"Deck ID: {response.get('deck-id')}\n"
115 |     result += f"Content: {response.get('content')}\n"
116 |     result += f"Created: {response.get('created-at', {}).get('date')}\n"
117 |     result += f"Updated: {response.get('updated-at', {}).get('date')}\n"
118 | 
119 |     if response.get("tags"):
120 |         result += f"Tags: {', '.join(response.get('tags'))}\n"
121 | 
122 |     return result
123 | 
124 | 
125 | @mcp.tool()
126 | async def mochi_update_card(
127 |     card_id: str,
128 |     content: str = None,
129 |     deck_id: str = None,
130 |     archived: bool = None,
131 |     tags: list = None,
132 | ) -> str:
133 |     """Update an existing Mochi card.
134 | 
135 |     Args:
136 |         api_key: Your Mochi API key
137 |         card_id: The ID of the card to update
138 |         content: Optional new markdown content
139 |         deck_id: Optional new deck ID to move the card to
140 |         archived: Optional boolean to archive/unarchive the card
141 |         tags: Optional list of tags to replace existing tags
142 |     """
143 |     url = f"{MOCHI_API_BASE}/cards/{card_id}"
144 | 
145 |     data = {}
146 |     if content is not None:
147 |         data["content"] = content
148 |     if deck_id is not None:
149 |         data["deck-id"] = deck_id
150 |     if archived is not None:
151 |         data["archived?"] = archived
152 |     if tags is not None:
153 |         data["manual-tags"] = tags
154 | 
155 |     response = await make_mochi_request(
156 |         url,
157 |         method="POST",
158 |         data=data,
159 |     )
160 | 
161 |     if not response or "error" in response:
162 |         return f"Error updating card: {response.get('error', 'Unknown error')}"
163 | 
164 |     return f"Card {card_id} updated successfully"
165 | 
166 | 
167 | @mcp.tool()
168 | async def mochi_delete_card(card_id: str) -> str:
169 |     """Delete a Mochi card permanently.
170 | 
171 |     Args:
172 |         api_key: Your Mochi API key
173 |         card_id: The ID of the card to delete
174 |     """
175 |     url = f"{MOCHI_API_BASE}/cards/{card_id}"
176 | 
177 |     response = await make_mochi_request(url, method="DELETE")
178 | 
179 |     if response and "error" in response:
180 |         return f"Error deleting card: {response.get('error')}"
181 | 
182 |     return f"Card {card_id} deleted successfully"
183 | 
184 | 
185 | if __name__ == "__main__":
186 |     mcp.run(transport="stdio")
187 | 
```