Skip to main content
Glama

Micro.blog Books MCP Server

by 7robots
server.py11.7 kB
"""Micro.blog Books API MCP Server using FastMCP.""" import json import logging from typing import Optional from urllib.parse import urljoin import httpx from fastmcp import FastMCP logger = logging.getLogger(__name__) BASE_URL = "https://micro.blog" class MicroBooksClient: """HTTP client for Micro.blog Books API.""" def __init__(self, bearer_token: str) -> None: self.bearer_token = bearer_token self.headers = { "Authorization": f"Bearer {bearer_token}", "User-Agent": "Micro Books MCP Server", "Content-Type": "application/x-www-form-urlencoded", } async def get_bookshelves(self) -> dict: """Get all bookshelves.""" async with httpx.AsyncClient() as client: response = await client.get( urljoin(BASE_URL, "/books/bookshelves"), headers=self.headers, ) response.raise_for_status() return response.json() async def get_bookshelf_books(self, bookshelf_id: int) -> dict: """Get books in a specific bookshelf.""" async with httpx.AsyncClient() as client: response = await client.get( urljoin(BASE_URL, f"/books/bookshelves/{bookshelf_id}"), headers=self.headers, ) response.raise_for_status() return response.json() async def add_bookshelf(self, name: str) -> dict: """Add a new bookshelf.""" async with httpx.AsyncClient() as client: response = await client.post( urljoin(BASE_URL, "/books/bookshelves"), headers=self.headers, data={"name": name}, ) response.raise_for_status() return {"success": True, "message": f"Bookshelf '{name}' created successfully"} async def rename_bookshelf(self, bookshelf_id: int, name: str) -> dict: """Rename a bookshelf.""" async with httpx.AsyncClient() as client: response = await client.post( urljoin(BASE_URL, f"/books/bookshelves/{bookshelf_id}"), headers=self.headers, data={"name": name}, ) response.raise_for_status() return {"success": True, "message": f"Bookshelf renamed to '{name}' successfully"} async def add_book( self, title: str, author: str, bookshelf_id: int, isbn: Optional[str] = None, cover_url: Optional[str] = None, ) -> dict: """Add a new book.""" data = { "title": title, "author": author, "bookshelf_id": str(bookshelf_id), } if isbn: data["isbn"] = isbn if cover_url: data["cover_url"] = cover_url async with httpx.AsyncClient() as client: response = await client.post( urljoin(BASE_URL, "/books"), headers=self.headers, data=data, ) response.raise_for_status() return {"success": True, "message": f"Book '{title}' by {author} added successfully"} async def move_book(self, book_id: int, bookshelf_id: int) -> dict: """Move a book to a different bookshelf.""" async with httpx.AsyncClient() as client: response = await client.post( urljoin(BASE_URL, f"/books/bookshelves/{bookshelf_id}/assign"), headers=self.headers, data={"book_id": str(book_id)}, ) response.raise_for_status() return {"success": True, "message": f"Book moved to bookshelf {bookshelf_id} successfully"} async def remove_book(self, bookshelf_id: int, book_id: int) -> dict: """Remove a book from a bookshelf.""" async with httpx.AsyncClient() as client: response = await client.delete( urljoin(BASE_URL, f"/books/bookshelves/{bookshelf_id}/remove/{book_id}"), headers=self.headers, ) response.raise_for_status() return {"success": True, "message": "Book removed from bookshelf successfully"} async def change_book_cover(self, bookshelf_id: int, book_id: int, cover_url: str) -> dict: """Change the cover for a book.""" async with httpx.AsyncClient() as client: response = await client.post( urljoin(BASE_URL, f"/books/bookshelves/{bookshelf_id}/cover/{book_id}"), headers=self.headers, data={"cover_url": cover_url}, ) response.raise_for_status() return {"success": True, "message": "Book cover updated successfully"} async def get_reading_goals(self) -> dict: """Get reading goals.""" async with httpx.AsyncClient() as client: response = await client.get( urljoin(BASE_URL, "/books/goals"), headers=self.headers, ) response.raise_for_status() return response.json() async def get_goal_progress(self, goal_id: int) -> dict: """Get books list progress toward a goal.""" async with httpx.AsyncClient() as client: response = await client.get( urljoin(BASE_URL, f"/books/goals/{goal_id}"), headers=self.headers, ) response.raise_for_status() return response.json() async def update_reading_goal(self, goal_id: int, value: int, progress: Optional[int] = None) -> dict: """Update reading goal.""" data = {"value": str(value)} if progress is not None: data["progress"] = str(progress) async with httpx.AsyncClient() as client: response = await client.post( urljoin(BASE_URL, f"/books/goals/{goal_id}"), headers=self.headers, data=data, ) response.raise_for_status() return {"success": True, "message": "Reading goal updated successfully"} def create_server(bearer_token: str) -> FastMCP: """Create the FastMCP server.""" mcp = FastMCP("Micro Books API") client = MicroBooksClient(bearer_token) @mcp.tool() async def get_bookshelves() -> str: """Get all bookshelves from Micro.blog.""" try: result = await client.get_bookshelves() return json.dumps(result, indent=2) except Exception: logger.exception("Failed to get bookshelves") raise @mcp.tool() async def get_bookshelf_books(bookshelf_id: int) -> str: """Get books in a specific bookshelf. Args: bookshelf_id: The ID of the bookshelf to get books from """ try: result = await client.get_bookshelf_books(bookshelf_id) return json.dumps(result, indent=2) except Exception: logger.exception("Failed to get bookshelf books") raise @mcp.tool() async def add_bookshelf(name: str) -> str: """Add a new bookshelf. Args: name: The name of the new bookshelf """ try: result = await client.add_bookshelf(name) return json.dumps(result, indent=2) except Exception: logger.exception("Failed to add bookshelf") raise @mcp.tool() async def rename_bookshelf(bookshelf_id: int, name: str) -> str: """Rename a bookshelf. Args: bookshelf_id: The ID of the bookshelf to rename name: The new name for the bookshelf """ try: result = await client.rename_bookshelf(bookshelf_id, name) return json.dumps(result, indent=2) except Exception: logger.exception("Failed to rename bookshelf") raise @mcp.tool() async def add_book( title: str, author: str, bookshelf_id: int, isbn: Optional[str] = None, cover_url: Optional[str] = None, ) -> str: """Add a new book. Args: title: The title of the book author: The author of the book bookshelf_id: The ID of the bookshelf to add the book to isbn: The ISBN of the book (optional) cover_url: URL to the book cover image (optional) """ try: result = await client.add_book(title, author, bookshelf_id, isbn, cover_url) return json.dumps(result, indent=2) except Exception: logger.exception("Failed to add book") raise @mcp.tool() async def move_book(book_id: int, bookshelf_id: int) -> str: """Move a book to a different bookshelf. Args: book_id: The ID of the book to move bookshelf_id: The ID of the target bookshelf """ try: result = await client.move_book(book_id, bookshelf_id) return json.dumps(result, indent=2) except Exception: logger.exception("Failed to move book") raise @mcp.tool() async def remove_book(bookshelf_id: int, book_id: int) -> str: """Remove a book from a bookshelf. Args: bookshelf_id: The ID of the bookshelf book_id: The ID of the book to remove """ try: result = await client.remove_book(bookshelf_id, book_id) return json.dumps(result, indent=2) except Exception: logger.exception("Failed to remove book") raise @mcp.tool() async def change_book_cover(bookshelf_id: int, book_id: int, cover_url: str) -> str: """Change the cover for a book. Args: bookshelf_id: The ID of the bookshelf book_id: The ID of the book cover_url: URL to the new cover image """ try: result = await client.change_book_cover(bookshelf_id, book_id, cover_url) return json.dumps(result, indent=2) except Exception: logger.exception("Failed to change book cover") raise @mcp.tool() async def get_reading_goals() -> str: """Get reading goals.""" try: result = await client.get_reading_goals() return json.dumps(result, indent=2) except Exception: logger.exception("Failed to get reading goals") raise @mcp.tool() async def get_goal_progress(goal_id: int) -> str: """Get books list progress toward a goal. Args: goal_id: The ID of the reading goal """ try: result = await client.get_goal_progress(goal_id) return json.dumps(result, indent=2) except Exception: logger.exception("Failed to get goal progress") raise @mcp.tool() async def update_reading_goal(goal_id: int, value: int, progress: Optional[int] = None) -> str: """Update reading goal. Args: goal_id: The ID of the reading goal value: The target number of books for the goal progress: The current progress (number of books read, optional) """ try: result = await client.update_reading_goal(goal_id, value, progress) return json.dumps(result, indent=2) except Exception: logger.exception("Failed to update reading goal") raise return mcp

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/7robots/micro-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server