readeck-mcp
by alozur
README.md
# Readeck MCP
An MCP server that turns a [Readeck](https://readeck.org) library into a
**read/write knowledge base for AI agents**: agents can push the HTML they
generate straight into Readeck (auto-tagged by an LLM) and later retrieve and
read those documents back. Built with [FastMCP](https://gofastmcp.com) — the
same code runs as a local **stdio** server or a remote **HTTP** server behind a
reverse proxy.
Unlike existing Readeck MCPs (which are read-only reading assistants), this one
**ingests agent-generated HTML** and **auto-labels it with an LLM** from a fixed
taxonomy.
## Tools
| Tool | Purpose |
|------|---------|
| `readeck_save(html, title, labels?, url?, auto_label?)` | Save raw HTML as a readable bookmark; auto-labels when no labels are given. |
| `readeck_read(query, only_own?, max_results?, max_chars?)` | Retrieval: find the best-matching document(s) and return their full readable text. |
| `readeck_search(query, limit?)` | Search the library (metadata only). |
| `readeck_list_labels()` | List existing labels with counts. |
`readeck_save` uses Readeck's JSON create API with the raw-HTML `html` field
(base64/data-URI do **not** work). It sends a browser User-Agent so a WAF (e.g.
Cloudflare error 1010) doesn't block it, and polls briefly to return the new
bookmark `id`, `word_count` and `reading_url`.
## Auto-labeling
When you call `readeck_save` without `labels`, an LLM classifies the document.
It's **injection-resistant by design**: the model may only pick from a fixed
taxonomy (`LABEL_TAXONOMY`), and the result is intersected with that list
server-side, so out-of-vocabulary labels are impossible. An origin label
(`ORIGIN_LABEL`, default `propio`) is always added by code, never the LLM.
Provider-agnostic (OpenAI-compatible or Anthropic). Works with free tiers such
as Groq — for reasoning models like `gpt-oss` set a generous `LLM_MAX_TOKENS`
and the server sends `reasoning_effort: low` automatically.
## Guardrails
- **Fail-closed HTTP auth**: over HTTP transport every tool call requires
`Authorization: Bearer $MCP_AUTH_TOKEN` (constant-time compare).
- Input validation: HTML size cap, non-HTML rejection, URL-scheme allowlist,
label sanitization + cap.
- Token-bucket rate limiting per process.
- LLM calls retry on 429/5xx with backoff and degrade gracefully (labeling is
best-effort; a failure never blocks the save).
## Configuration (environment)
| Variable | Default | Notes |
|----------|---------|-------|
| `READECK_URL` | `http://localhost:8000` | Readeck base URL. |
| `READECK_TOKEN` | — | **Required.** Readeck → Settings → API tokens. |
| `READECK_MCP_TRANSPORT` | `stdio` | `stdio` or `http`. |
| `READECK_MCP_HOST` / `READECK_MCP_PORT` | `0.0.0.0` / `8787` | http mode only. |
| `MCP_AUTH_TOKEN` | — | Required in http mode (fail-closed bearer). |
| `AUTO_LABEL` | `true` | Enable LLM auto-labeling. |
| `ORIGIN_LABEL` | `propio` | Always-added origin label. |
| `LABEL_TAXONOMY` | see `config.py` | Comma-separated closed vocabulary. |
| `MAX_AUTO_LABELS` | `3` | Max LLM topics per doc. |
| `LLM_PROVIDER` | `anthropic` | `anthropic` or `openai` (OpenAI-compatible). |
| `LLM_BASE_URL` | `https://api.anthropic.com` | Include the version prefix for openai (e.g. `.../v1`). |
| `LLM_MODEL` | `claude-haiku-4-5` | Any chat model on the chosen provider. |
| `LLM_API_KEY` | — | Key for the LLM provider. |
| `LLM_MAX_TOKENS` / `LLM_TEXT_CHARS` | `2000` / `3000` | Output budget / input text sent. |
## Local (stdio)
```bash
READECK_TOKEN=... uv run --script server.py --selftest # smoke test
```
Register with an MCP client (`mcpServers`), e.g.:
```json
{
"mcpServers": {
"readeck": {
"command": "uv",
"args": ["run", "--script", "/path/to/readeck-mcp/server.py"],
"env": { "READECK_URL": "https://readeck.example.com", "READECK_TOKEN": "..." }
}
}
}
```
## Remote (HTTP / Docker)
Run with `READECK_MCP_TRANSPORT=http` (see `Dockerfile`). Put it behind your
reverse proxy / tunnel at e.g. `https://mcp-readeck.example.com`. When
co-located with Readeck, set `READECK_URL=http://readeck:8000` to skip the public
round-trip. Point clients at the endpoint:
```json
{
"mcpServers": {
"readeck": {
"type": "http",
"url": "https://mcp-readeck.example.com/mcp",
"headers": { "Authorization": "Bearer <MCP_AUTH_TOKEN>" }
}
}
}
```
> **Note:** use the endpoint path **without** a trailing slash (`/mcp`, not
> `/mcp/`) — some proxies turn the trailing-slash redirect into a 307 that breaks
> streamable-HTTP clients.
Always set a strong `MCP_AUTH_TOKEN` before exposing the endpoint — the token
grants write access to your library.
## License
MIT
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues