FreshRSS MCP Server for AI Agents
by zhiyuan7
README.md
# FreshRSS MCP Server for AI Agents
An MCP server that wraps the FreshRSS Google Reader API, exposing feed management, safe full-article extraction, and YouTube transcript retrieval as tools for AI agents. It uses **Streamable HTTP** transport and works with Codex and other MCP clients.
Token-optimized: returns only essential fields with configurable summary truncation, achieving ~90% reduction vs raw RSS XML payloads.
This repository is derived from [ChrisLAS/freshrss-mcp](https://github.com/ChrisLAS/freshrss-mcp), created by Chris Fisher and released under the MIT License. The original copyright and license are preserved. See [NOTICE](NOTICE.md) for the upstream attribution and a summary of the added functionality.
## Added agent-reading capabilities
- `fetch_full_article`: extracts readable text from public article pages with redirect, size, content-type, and SSRF protections.
- `get_youtube_transcript`: retrieves existing YouTube captions through Supadata, optionally permits paid AI transcription, and caches results locally.
- Docker deployment for small always-on hosts such as Raspberry Pi.
- Tests for URL validation, secret masking, article extraction, transcript normalization, and tool error boundaries.
---
## NixOS Installation
The flake exports a NixOS module that runs the server as a hardened systemd service.
### 1. Add the flake input
In your host's `flake.nix`:
```nix
inputs = {
freshrss-mcp.url = "github:ChrisLAS/freshrss-mcp";
freshrss-mcp.inputs.nixpkgs.follows = "nixpkgs";
};
```
Pass it through to your NixOS configuration:
```nix
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
./system.nix
freshrss-mcp.nixosModules.default
];
};
```
### 2. Configure the service
In your host config (e.g. `system.nix`):
```nix
services.freshrss-mcp-server = {
enable = true;
freshRssUrl = "https://freshrss.example.com";
username = "youruser";
passwordFile = "/home/youruser/.config/secrets/freshrss-mcp";
port = 3005;
host = "0.0.0.0"; # or "127.0.0.1" for loopback only
};
```
The password file must use systemd EnvironmentFile format:
```
FRESHRSS_PASSWORD=your-api-password-here
```
### 3. Rebuild
```bash
sudo nixos-rebuild switch
systemctl status freshrss-mcp-server # verify it's running
```
The service runs as a sandboxed DynamicUser with hardened security settings.
---
## OpenClaw Gateway Setup
Register the FreshRSS MCP server in the `openclaw-mcp-bridge` plugin inside `~/.openclaw/openclaw.json`. Add a server entry to the existing `servers` array:
```json
{
"plugins": {
"entries": {
"openclaw-mcp-bridge": {
"enabled": true,
"config": {
"servers": [
{
"name": "FreshRSS",
"url": "http://127.0.0.1:3005",
"prefix": "freshrss"
}
]
}
}
}
}
}
```
Then restart the gateway: `openclaw gateway restart`
Tools will appear with the `freshrss_` prefix, e.g. `freshrss_list_feeds`, `freshrss_get_unread_articles`.
---
## For AI Agents: Setup & Installation Guide
This section is written for agentic LLM sessions that need to deploy, configure, or extend this server.
### Quick Start (non-NixOS)
```bash
# Clone
git clone https://github.com/ChrisLAS/freshrss-mcp.git
cd freshrss-mcp
# Install deps (requires Python 3.12+ and uv)
uv sync
# Set required env vars
export FRESHRSS_URL="https://freshrss.example.com"
export FRESHRSS_USERNAME="youruser"
export FRESHRSS_PASSWORD="yourpass"
# Start server (binds to 127.0.0.1:8000/mcp by default)
uv run freshrss-mcp
# Override host/port
export MCP_SERVER_HOST="0.0.0.0"
export MCP_SERVER_PORT=3005
uv run freshrss-mcp
```
### Docker
```bash
docker build -t freshrss-mcp .
docker run --rm -p 127.0.0.1:8000:8000 \
-e FRESHRSS_URL="https://freshrss.example.com" \
-e FRESHRSS_USERNAME="youruser" \
-e FRESHRSS_PASSWORD="your-api-password" \
-e MCP_SERVER_HOST="0.0.0.0" \
-v freshrss-mcp-cache:/cache \
freshrss-mcp
```
Add `SUPADATA_API_KEY` only if you want the YouTube transcript tool. Never commit API keys or FreshRSS credentials to the repository.
### Quick Start (NixOS)
```bash
# Enter dev shell (provides uv, python, ruff)
nix develop
# Install deps and run tests
uv sync
uv run pytest -v
# Start the server
export FRESHRSS_URL="https://freshrss.example.com"
export FRESHRSS_USERNAME="youruser"
export FRESHRSS_PASSWORD="yourpass"
uv run freshrss-mcp
```
On NixOS, the Nix devshell sets `UV_PYTHON_DOWNLOADS=never` and `UV_PYTHON` automatically to avoid dynamically linked binary issues.
### Verify the Server
```bash
# List tools via JSON-RPC
curl -s http://127.0.0.1:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq .
# Call a tool
curl -s http://127.0.0.1:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_feeds","arguments":{}}}' | jq .
# MCP Inspector (interactive)
npx @modelcontextprotocol/inspector --url http://127.0.0.1:8000/mcp
```
### Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `FRESHRSS_URL` | Yes | — | FreshRSS instance URL |
| `FRESHRSS_USERNAME` | Yes | — | FreshRSS username |
| `FRESHRSS_PASSWORD` | Yes | — | FreshRSS API password |
| `FRESHRSS_API_PATH` | No | `/api/greader.php` | Google Reader API path |
| `SUPADATA_API_KEY` | No | — | Enables YouTube captions and optional AI transcription via Supadata |
| `MCP_TRANSCRIPT_CACHE_DIR` | No | `/cache` | Persistent transcript cache directory |
| `MCP_SERVER_HOST` | No | `127.0.0.1` | Bind address |
| `MCP_SERVER_PORT` | No | `8000` | Bind port |
### Available Tools
| Tool | Description | Key Args |
|------|-------------|----------|
| `fetch_full_article` | Safely extract readable text from a public article URL | `url`, `max_chars` |
| `get_youtube_transcript` | Retrieve and cache YouTube captions or authorized AI transcription | `video_url`, `language`, `max_chars`, `allow_ai_generation` |
| `get_unread_articles` | Fetch unread articles with filtering | `limit`, `feed_ids`, `since_timestamp`, `max_summary_length` |
| `get_articles_by_feed` | Articles from a specific feed | `feed_id`, `limit`, `include_read` |
| `search_articles` | Client-side keyword search in titles/summaries | `query`, `limit`, `feed_ids` |
| `list_feeds` | All subscribed feeds with unread counts | — |
| `get_feed_info` | Detailed info for one feed | `feed_id` |
| `get_feed_stats` | Statistics for all feeds | — |
| `mark_as_read` | Batch mark articles as read | `article_ids` |
| `mark_as_unread` | Batch mark articles as unread | `article_ids` |
| `star_article` | Star/favorite an article | `article_id` |
| `unstar_article` | Remove star from an article | `article_id` |
### Architecture Notes
- **Transport**: Streamable HTTP (POST `/mcp`), not stdio. The OpenClaw `openclaw-mcp-bridge` plugin discovers tools via HTTP.
- **Auth**: Lazy authentication — the FreshRSS client authenticates on the first API call, not at startup.
- **Error handling**: Every tool catches all exceptions and returns `"Error: ..."` strings. MCP protocol never sees uncaught exceptions.
- **Config**: pydantic-settings `BaseSettings` with `SecretStr` for the password. Validation happens at startup.
- **Dependencies**: `aiohttp`, `fastmcp`, `httpx`, `pydantic-settings`, and `trafilatura`. No version pins.
- **Secrets**: FreshRSS and Supadata credentials are read from environment variables and represented with `SecretStr`.
- **Tests**: Unit tests cover config, client, tools, models, article extraction, and transcript handling. Run with `uv run pytest -v`.
### Project Structure
```
src/freshrss_mcp/
server.py — FastMCP entry point, signal handlers, streamable-http transport
tools.py — MCP tool definitions with error boundaries
client.py — Async FreshRSS Google Reader API client (httpx)
config.py — pydantic-settings config from env vars
models.py — Article and Feed dataclasses
article_fetcher.py — public-only HTTP fetch and readable-text extraction
youtube_transcript.py — Supadata transcript client and persistent cache
tests/
test_config.py — Config validation, defaults, secret masking
test_client.py — Auth, feeds, articles, ID extraction
test_tools.py — Tool happy paths + error boundaries
test_models.py — Serialization, construction, edge cases
test_article_fetcher.py — URL safety, redirect, and extraction behavior
test_youtube_transcript.py — YouTube URL parsing and transcript normalization
```
## Known Limitations
- **Client-side search**: FreshRSS API lacks server-side search; `search_articles` fetches articles then filters locally.
- **No pagination**: Article fetches use a single `limit` parameter without cursor-based pagination.
- **No real-time updates**: The server is request-driven; no push/webhook mechanism for new articles.
## License
MIT. The upstream copyright notice remains in [LICENSE](LICENSE); modification details are in [NOTICE](NOTICE.md).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues