Micro.blog Books MCP Server
# Micro.blog Books MCP Server
An MCP (Model Context Protocol) server built with FastMCP that provides access to the Micro.blog Books API. Deployed on [Modal](https://modal.com) and fronted by Cloudflare at **`https://books.mcp.7robots.org/mcp`** (a Worker reverse-proxy that rewrites the `Host` header to the Modal origin `robots--micro-mcp-server-web.modal.run`). The Cloudflare FQDN is the canonical address; the Modal URL still works directly.
This is a **single-user personal server**. Because it has write access to your books database, the MCP endpoint is gated by **Okta OAuth** — the same pattern as the other servers in this fleet. OAuth (rather than a static bearer header) is what lets the server be added as a custom connector on claude.ai, which then syncs to every signed-in Claude client (web, desktop, mobile). Your Micro.blog API token (`MICRO_BLOG_BEARER_TOKEN`) is separate and stays server-side — it authenticates the server to micro.blog and never reaches MCP clients.
## Features
This MCP server provides the following tools for managing your Micro.blog books:
### Bookshelf Management
- **get_bookshelves**: Get all your bookshelves
- **get_bookshelf_books**: Get books in a specific bookshelf
- **add_bookshelf**: Create a new bookshelf
- **rename_bookshelf**: Rename an existing bookshelf
### Book Management
- **add_book**: Add a new book to a bookshelf
- **move_book**: Move a book between bookshelves
- **remove_book**: Remove a book from a bookshelf
- **change_book_cover**: Update a book's cover image
### Reading Goals
- **get_reading_goals**: Get your reading goals
- **get_goal_progress**: Get progress toward a specific reading goal
- **update_reading_goal**: Update a reading goal's target or progress
## Deployment
This server is deployed on [Modal](https://modal.com).
### Prerequisites
- A [Micro.blog](https://micro.blog) account with API access
- A bearer token from your Micro.blog account settings
### Environment Variables
Two Modal secrets, kept separate so the micro.blog credential and the OAuth/storage config rotate independently: `micro-blog-bearer-token` (the API token) and `micro-mcp-server-secrets` (everything else below).
| Variable | Description | Required |
|----------|-------------|----------|
| `MICRO_BLOG_BEARER_TOKEN` | Your Micro.blog API bearer token (server → micro.blog; stays server-side) | Yes |
| `OKTA_CLIENT_ID` | Okta OAuth2 client ID | Yes (for auth) |
| `OKTA_CLIENT_SECRET` | Okta OAuth2 client secret | Yes (for auth) |
| `OKTA_DOMAIN` | Okta instance domain (e.g. `https://integrator-9607059.okta.com`) | Yes (for auth) |
| `MCP_BASE_URL` | Public Modal URL (no `/mcp` suffix) | Yes (for auth) |
| `OKTA_ISSUER` | OAuth2 issuer (defaults to `{OKTA_DOMAIN}/oauth2/default`) | No |
| `JWT_SIGNING_KEY` | Fixed key for signing MCP JWTs (auto-generated key is ephemeral across cold starts). Generate with `python -c "import secrets; print(secrets.token_urlsafe(32))"` | Production |
| `STORAGE_ENCRYPTION_KEY` | Fernet key encrypting OAuth state in modal.Dict. Generate with `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` | Production |
Auth is opt-in — when `OKTA_CLIENT_SECRET` is unset, the server runs without authentication (local dev only).
### Deploy to Modal
```bash
uv sync
# Secret 1 — your micro.blog API token (server -> micro.blog):
uv run modal secret create micro-blog-bearer-token MICRO_BLOG_BEARER_TOKEN=your_microblog_token
# Secret 2 — Okta + storage config (clients -> this server via OAuth):
uv run modal secret create micro-mcp-server-secrets \
OKTA_CLIENT_ID=... OKTA_CLIENT_SECRET=... OKTA_DOMAIN=https://your.okta.com \
MCP_BASE_URL=https://books.mcp.7robots.org \
JWT_SIGNING_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')" \
STORAGE_ENCRYPTION_KEY="$(python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')"
# Deploy (clean stale bytecode first — Modal's lazy mounts can serve stale .pyc):
find . -type d -name __pycache__ -exec rm -rf {} +
uv run modal deploy modal_app.py
```
`MCP_BASE_URL` is the Cloudflare FQDN `https://books.mcp.7robots.org` (no `/mcp` suffix) — the public hostname clients use. The Cloudflare Worker reverse-proxies it to the Modal origin.
#### Operator checklist (manual steps, outside the agent/code)
These must be done by a human in the Modal/Okta consoles — they are not part of the repo and cannot be scripted from here:
1. **Okta app** — in the OAuth application used for this server (this fleet reuses one dev-integrator app):
- Add the Sign-in redirect URI `https://books.mcp.7robots.org/auth/callback` (the Cloudflare FQDN; `/auth/callback` is OIDCProxy's default).
- On the access policy, confirm **Refresh Token** is an allowed grant type (required for `offline_access`; without it users re-auth ~hourly).
- Note the client ID, client secret, and domain for the secret.
2. **Generate keys** — `JWT_SIGNING_KEY` and `STORAGE_ENCRYPTION_KEY` (commands above); store in a password manager.
3. **Create both Modal secrets** (see commands above). `MICRO_BLOG_BEARER_TOKEN` is your micro.blog token.
4. **First deploy**, set `MCP_BASE_URL` to the Cloudflare FQDN `https://books.mcp.7robots.org`, and **redeploy**.
5. **Verify** — connect from Claude (OAuth login), then idle ~5–8 min for scale-to-zero and call a tool again to confirm no re-auth prompt (validates the `modal.Dict` persistence).
### Local Development
```bash
# Install dependencies
uv sync
# Run the server locally
export MICRO_BLOG_BEARER_TOKEN="your_token_here"
uv run fastmcp run server.py --transport http --port 8000
# Or run directly
uv run python server.py
```
## Connecting a client
The server is OAuth-gated, so add it as a **custom connector** rather than via a config file:
1. In claude.ai (or Claude Desktop) → **Settings → Connectors → Add custom connector**.
2. Enter the URL: `https://books.mcp.7robots.org/mcp`
3. Complete the Okta login when prompted.
Added on claude.ai, the connector is account-level and syncs to every signed-in Claude client (web, desktop, mobile). On first connection you'll be redirected to Okta to authenticate.
## Usage
Once connected, you can use the server from Claude Desktop, claude.ai, or any OAuth-capable MCP client.
Example prompts:
- "Show me all my bookshelves"
- "Add 'Project Hail Mary' by Andy Weir to my Currently Reading bookshelf"
- "What are my reading goals for this year?"
- "Move book ID 79 to bookshelf ID 23"
## API Reference
All tools return JSON responses from the Micro.blog API. The server handles authentication automatically using your bearer token.
For more details about the underlying API, see the [Micro.blog Books API documentation](https://help.micro.blog/t/books-api/280).
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
TDQS
Scored across 11 tools
Each tool has a clearly distinct purpose with no ambiguity. Tools like add_book, move_book, remove_book, and change_book_cover target specific book operations, while get_bookshelves, rename_bookshelf, and add_bookshelf handle bookshelf management, and get_reading_goals and update_reading_goal focus on goals. The descriptions reinforce these distinct roles, making misselection unlikely.
All tools follow a consistent verb_noun naming pattern throughout, such as add_book, get_bookshelves, rename_bookshelf, and update_reading_goal. There are no deviations in style or convention, making the tool names predictable and easy to understand at a glance.
With 11 tools, the count is well-scoped for managing books, bookshelves, and reading goals. Each tool earns its place by covering essential CRUD operations (e.g., add, get, move, remove, rename, update) without redundancy, fitting neatly within the typical 3-15 tool range for a focused domain.
The tool surface provides complete coverage for the domain of book and bookshelf management. It includes full CRUD for books (add, get, move, remove, change cover) and bookshelves (add, get, rename), plus reading goal operations (get, update). There are no obvious gaps, enabling agents to handle all core workflows without dead ends.