Skip to content

Commit 7e9d275

Browse files
ChrisLAScursoragent
andcommitted
Add NixOS module, update docs and flake for host integration
- Restore nixosModules.default in flake.nix with hardened systemd service (DynamicUser, EnvironmentFile, sandbox options) - Update flake.lock to current nixos-unstable - Rewrite AGENTS.md for new agentic sessions with architecture notes - Rewrite README.md with NixOS install, OpenClaw setup, and agent guide Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 7dfec32 commit 7e9d275

File tree

4 files changed

+392
-95
lines changed

4 files changed

+392
-95
lines changed

AGENTS.md

Lines changed: 120 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,135 @@
22

33
This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started.
44

5-
## Quick Reference
5+
## What This Project Is
6+
7+
FreshRSS MCP server using **Streamable HTTP** transport, designed for the **OpenClaw** gateway's `openclaw-mcp-bridge` plugin. It wraps the FreshRSS Google Reader API and exposes 10 MCP tools for RSS feed management.
8+
9+
**Transport**: Streamable HTTP (POST to `/mcp`) -- NOT stdio. The OpenClaw MCP bridge discovers tools via HTTP POST with JSON-RPC 2.0, not by spawning a subprocess.
10+
11+
**Runtime**: Python 3.12+, managed by `uv`. Deployed on NixOS as a systemd service.
12+
13+
## Project Layout
614

7-
```bash
8-
bd ready # Find available work
9-
bd show <id> # View issue details
10-
bd update <id> --status in_progress # Claim work
11-
bd close <id> # Complete work
12-
bd sync # Sync with git
1315
```
16+
freshrss-mcp/
17+
pyproject.toml # uv/PEP 621 — entry point: freshrss-mcp
18+
uv.lock # Committed lockfile
19+
.python-version # 3.13
20+
flake.nix # Dev shell + NixOS module (systemd service)
21+
src/freshrss_mcp/
22+
server.py # FastMCP entry point (streamable-http transport)
23+
tools.py # 10 MCP tool definitions with error boundaries
24+
client.py # FreshRSS Google Reader API client (async, httpx)
25+
config.py # pydantic-settings config from env vars
26+
models.py # Article and Feed dataclasses
27+
tests/ # 67 unit tests
28+
test_config.py # Config validation, defaults, secret masking
29+
test_client.py # Auth, feeds, articles, ID extraction, edge cases
30+
test_tools.py # Every tool happy path + error boundary
31+
test_models.py # Serialization, construction, mutability
32+
```
33+
34+
## Key Architecture Decisions
1435

15-
## Project Overview
36+
1. **Lazy authentication**: `client.py` calls `_ensure_authenticated()` before every API method. No need to pre-auth at startup.
1637

17-
FreshRSS MCP server using **Streamable HTTP** transport for OpenClaw integration.
38+
2. **Error boundaries**: Every tool in `tools.py` catches all exceptions and returns `"Error: ..."` strings. MCP protocol never sees uncaught exceptions.
1839

19-
**Key files:**
20-
- `src/freshrss_mcp/server.py` — Entry point, FastMCP with streamable-http
21-
- `src/freshrss_mcp/tools.py` — All MCP tool definitions
22-
- `src/freshrss_mcp/client.py` — FreshRSS Google Reader API client
23-
- `src/freshrss_mcp/config.py` — pydantic-settings config from env vars
24-
- `tests/` — 67 tests across config, client, tools, and models
40+
3. **pydantic-settings**: `config.py` uses `BaseSettings` with `SecretStr` for the password. Missing env vars produce clear validation errors at startup.
41+
42+
4. **Single client instance**: `FreshRSSClient` is created once in `server.py` and shared across all tool calls. No per-call client creation.
43+
44+
5. **No version pins**: `pyproject.toml` has no version constraints on dependencies, per the MCP build spec.
45+
46+
## How to Run
2547

26-
**Running:**
2748
```bash
28-
uv sync && uv run pytest -v # test
29-
uv run freshrss-mcp # start server
49+
# Dev
50+
uv sync && uv run pytest -v
51+
52+
# Start server (needs env vars)
53+
export FRESHRSS_URL="https://freshrss.trailertrash.io"
54+
export FRESHRSS_USERNAME="chrisf"
55+
export FRESHRSS_PASSWORD="..."
56+
export MCP_SERVER_PORT=8765
57+
uv run freshrss-mcp
58+
59+
# Server binds to http://127.0.0.1:8765/mcp
3060
```
3161

32-
**Transport:** Streamable HTTP (not stdio). OpenClaw's `openclaw-mcp-bridge` plugin connects via HTTP POST to `/mcp`.
62+
## NixOS Integration
63+
64+
The flake exports `nixosModules.default`. Host config (rvbee) at `/home/chrisf/build/config`:
65+
66+
```nix
67+
# In flake.nix inputs:
68+
freshrss-mcp.url = "github:ChrisLAS/freshrss-mcp";
69+
freshrss-mcp.inputs.nixpkgs.follows = "nixpkgs";
70+
71+
# In rvbee modules:
72+
freshrss-mcp.nixosModules.default
73+
74+
# In services config:
75+
services.freshrss-mcp-server = {
76+
enable = true;
77+
freshRssUrl = "https://freshrss.trailertrash.io";
78+
username = "chrisf";
79+
passwordFile = "/home/chrisf/.config/secrets/freshrss-mcp";
80+
port = 3005;
81+
host = "0.0.0.0";
82+
};
83+
```
84+
85+
The password file must be an env file: `FRESHRSS_PASSWORD=<value>`.
86+
87+
## OpenClaw Bridge Config
88+
89+
The server is consumed by the `openclaw-mcp-bridge` plugin in `~/.openclaw/openclaw.json`:
90+
91+
```json
92+
{
93+
"plugins": {
94+
"entries": {
95+
"openclaw-mcp-bridge": {
96+
"config": {
97+
"servers": [
98+
{
99+
"name": "FreshRSS",
100+
"url": "http://127.0.0.1:3005",
101+
"prefix": "freshrss"
102+
}
103+
]
104+
}
105+
}
106+
}
107+
}
108+
}
109+
```
110+
111+
Tools appear as: `freshrss_list_feeds`, `freshrss_get_unread_articles`, etc.
112+
113+
## Quick Reference
114+
115+
```bash
116+
bd ready # Find available work
117+
bd show <id> # View issue details
118+
bd update <id> --status in_progress # Claim work
119+
bd close <id> # Complete work
120+
bd sync # Sync with git
121+
```
33122

34123
## Landing the Plane (Session Completion)
35124

36-
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
37-
38-
**MANDATORY WORKFLOW:**
39-
40-
1. **File issues for remaining work** - Create issues for anything that needs follow-up
41-
2. **Run quality gates** (if code changed) - Tests, linters, builds
42-
3. **Update issue status** - Close finished work, update in-progress items
43-
4. **PUSH TO REMOTE** - This is MANDATORY:
44-
```bash
45-
git pull --rebase
46-
bd sync
47-
git push
48-
git status # MUST show "up to date with origin"
49-
```
50-
5. **Clean up** - Clear stashes, prune remote branches
51-
6. **Verify** - All changes committed AND pushed
52-
7. **Hand off** - Provide context for next session
53-
54-
**CRITICAL RULES:**
55-
- Work is NOT complete until `git push` succeeds
56-
- NEVER stop before pushing - that leaves work stranded locally
57-
- NEVER say "ready to push when you are" - YOU must push
58-
- If push fails, resolve and retry until it succeeds
125+
When ending a session, you MUST:
126+
127+
1. File issues for remaining work
128+
2. Run quality gates: `uv run pytest -v` and `ruff check src/ tests/`
129+
3. Update issue status via `bd close`
130+
4. Push to remote:
131+
```bash
132+
git pull --rebase && bd sync && git push
133+
git status # MUST show "up to date with origin"
134+
```
135+
136+
Work is NOT complete until `git push` succeeds.

0 commit comments

Comments
 (0)