diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..dea468d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,34 @@ +# AGENTS.md — Rainkeeper + +Rainkeeper is Simon's own open-source MCP server for Raindrop.io, described here for any agentic coding tool operating on this repo. It fixes two bugs in existing community alternatives: it works on free Raindrop accounts (the official MCP requires Pro), and collection moves actually work (correct `{"collection": {"$id": n}}` request body format). MIT licence. + +**Language:** Python +**Framework:** FastMCP +**Repo:** `~/git/rainkeeper` +**Registered as:** `rainkeeper` in Claude Desktop and CLI (`mcp__rainkeeper__` tool prefix) + +## Scope +This repo's delta over generic Python/FastMCP competence: the Raindrop-specific API bug fixes, and the mandatory Superpowers development methodology for this project. Generic Python/FastMCP/MCP-server knowledge is assumed, not repeated. + +## Precedence +This file is authoritative for all rainkeeper work — no parent AGENTS.md exists inside this repo. Global developer and tool conventions apply where not overridden here; this file overrides defaults on rainkeeper-specific technical and process matters (e.g. mandatory TDD) regardless of recency. + +## Instructions + +**Commands:** +- Branch naming: `feat/`, `fix/`, `docs/`, `chore/`. All work on a feature branch, PR into `main` — never commit directly to `main`. +- Mandatory workflow for any new feature or significant change: `/brainstorming` (before any implementation, no exceptions for perceived simplicity) → `/writing-plans` (plan written to `docs/superpowers/specs/` and committed before coding) → `/test-driven-development` (no production code without a failing test first) → `/verification-before-completion` (before marking any task done) → `/finishing-a-development-branch` (before raising a PR). +- For bug fixes: write the failing test that reproduces the bug first, then fix. No exception. + +**Conventions:** +- Always call `list_collections` first in any Raindrop session — all move/file operations require numeric collection IDs, not names. +- The Superpowers plugin methodology (referenced via `/brainstorming`, `/writing-plans`, `/test-driven-development`, `/verification-before-completion`, `/finishing-a-development-branch`) is mandatory for all work on this project. + +## Negatives +Never skip a Superpowers hard gate without explicit instruction from Simon. Never write production code before a failing test exists — delete any code written before tests existed and start fresh, no exceptions. Exception: throwaway spikes for exploration/prototyping are exempt from TDD, but must be deleted before production implementation begins — do not adapt spike code into production code. + +## Expiry +The critical Raindrop API fix (`{"collection": {"$id": n}}` required for moves; `{"collectionId": n}` returns 200 OK but silently does nothing) and the 19-tool count across collections/raindrops/bulk-operations/search/tags — owner: Simon, last-verified: 2026-07-25, refresh interval: whenever Raindrop's API changes or tools are added/removed. Connects to Simon's Raindrop.io account (`simon-ives-au`). + +--- +[[Rainkeeper]] · [[Side Projects CLAUDE]] diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fc6a91e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,34 @@ +# CLAUDE.md — Rainkeeper + +Rainkeeper is Simon's own open-source MCP server for Raindrop.io. It fixes two bugs in existing community alternatives: it works on free Raindrop accounts (the official MCP requires Pro), and collection moves actually work (correct `{"collection": {"$id": n}}` request body format). MIT licence. + +**Language:** Python +**Framework:** FastMCP +**Repo:** `~/git/rainkeeper` +**Registered as:** `rainkeeper` in Claude Desktop and CLI (`mcp__rainkeeper__` tool prefix) + +## Scope +This repo's delta over generic Python/FastMCP competence: the Raindrop-specific API bug fixes, and the mandatory Superpowers development methodology for this project. Generic Python/FastMCP/MCP-server knowledge is assumed, not repeated. + +## Precedence +This file is authoritative for all rainkeeper work — no parent CLAUDE.md exists inside this repo. Global `~/.claude/CLAUDE.md` DOCTRINE and preferences apply where not overridden here; this file overrides the global defaults on rainkeeper-specific technical and process matters (e.g. mandatory TDD) regardless of recency. + +## Instructions + +**Commands:** +- Branch naming: `feat/`, `fix/`, `docs/`, `chore/`. All work on a feature branch, PR into `main` — never commit directly to `main`. +- Mandatory workflow for any new feature or significant change: `/brainstorming` (before any implementation, no exceptions for perceived simplicity) → `/writing-plans` (plan written to `docs/superpowers/specs/` and committed before coding) → `/test-driven-development` (no production code without a failing test first) → `/verification-before-completion` (before marking any task done) → `/finishing-a-development-branch` (before raising a PR). +- For bug fixes: write the failing test that reproduces the bug first, then fix. No exception. + +**Conventions:** +- Always call `list_collections` first in any Raindrop session — all move/file operations require numeric collection IDs, not names. +- The Superpowers plugin (`superpowers@claude-plugins-official`) methodology is mandatory for all work on this project. + +## Negatives +Never skip a Superpowers hard gate without explicit instruction from Simon. Never write production code before a failing test exists — delete any code written before tests existed and start fresh, no exceptions. Exception: throwaway spikes for exploration/prototyping are exempt from TDD, but must be deleted before production implementation begins — do not adapt spike code into production code. + +## Expiry +The critical Raindrop API fix (`{"collection": {"$id": n}}` required for moves; `{"collectionId": n}` returns 200 OK but silently does nothing) and the 19-tool count across collections/raindrops/bulk-operations/search/tags — owner: Simon, last-verified: 2026-07-25, refresh interval: whenever Raindrop's API changes or tools are added/removed. Connects to Simon's Raindrop.io account (`simon-ives-au`). + +--- +[[Rainkeeper]] · [[Side Projects CLAUDE]] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d00858f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.12-slim AS app +WORKDIR /app + +COPY pyproject.toml README.md ./ +COPY src/ ./src/ +RUN pip install --no-cache-dir . uvicorn + +EXPOSE 8001 +CMD ["python", "-m", "rainkeeper.http_server"] diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..424b6f0 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,34 @@ +# GEMINI.md — Rainkeeper + +Rainkeeper is Simon's own open-source MCP server for Raindrop.io, described here for Gemini operating directly on this repo via Antigravity. It fixes two bugs in existing community alternatives: it works on free Raindrop accounts (the official MCP requires Pro), and collection moves actually work (correct `{"collection": {"$id": n}}` request body format). MIT licence. + +**Language:** Python +**Framework:** FastMCP +**Repo:** `~/git/rainkeeper` +**Registered as:** `rainkeeper` in Claude Desktop and CLI (`mcp__rainkeeper__` tool prefix) + +## Scope +This repo's delta over generic Python/FastMCP competence: the Raindrop-specific API bug fixes, and the mandatory Superpowers development methodology for this project. Generic Python/FastMCP/MCP-server knowledge is assumed, not repeated. + +## Precedence +This file is authoritative for all rainkeeper work — no parent GEMINI.md exists inside this repo. General developer and tool conventions apply where not overridden here; this file overrides defaults on rainkeeper-specific technical and process matters (e.g. mandatory TDD) regardless of recency. + +## Instructions + +**Commands:** +- Branch naming: `feat/`, `fix/`, `docs/`, `chore/`. All work on a feature branch, PR into `main` — never commit directly to `main`. +- Mandatory workflow for any new feature or significant change: `/brainstorming` (before any implementation, no exceptions for perceived simplicity) → `/writing-plans` (plan written to `docs/superpowers/specs/` and committed before coding) → `/test-driven-development` (no production code without a failing test first) → `/verification-before-completion` (before marking any task done) → `/finishing-a-development-branch` (before raising a PR). +- For bug fixes: write the failing test that reproduces the bug first, then fix. No exception. + +**Conventions:** +- Always call `list_collections` first in any Raindrop session — all move/file operations require numeric collection IDs, not names. +- The Superpowers plugin methodology (referenced via `/brainstorming`, `/writing-plans`, `/test-driven-development`, `/verification-before-completion`, `/finishing-a-development-branch`) is mandatory for all work on this project. + +## Negatives +Never skip a Superpowers hard gate without explicit instruction from Simon. Never write production code before a failing test exists — delete any code written before tests existed and start fresh, no exceptions. Exception: throwaway spikes for exploration/prototyping are exempt from TDD, but must be deleted before production implementation begins — do not adapt spike code into production code. + +## Expiry +The critical Raindrop API fix (`{"collection": {"$id": n}}` required for moves; `{"collectionId": n}` returns 200 OK but silently does nothing) and the 19-tool count across collections/raindrops/bulk-operations/search/tags — owner: Simon, last-verified: 2026-07-25, refresh interval: whenever Raindrop's API changes or tools are added/removed. Connects to Simon's Raindrop.io account (`simon-ives-au`). + +--- +[[Rainkeeper]] · [[Side Projects CLAUDE]] diff --git a/pyproject.toml b/pyproject.toml index 7596287..f99928f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,10 +13,12 @@ dependencies = [ "fastmcp>=0.4.0", "httpx>=0.27.0", "python-dotenv>=1.0.0", + "uvicorn>=0.30.0", ] [project.scripts] rainkeeper = "rainkeeper.server:main" +rainkeeper-http = "rainkeeper.http_server:main" [tool.hatch.build.targets.wheel] packages = ["src/rainkeeper"] diff --git a/src/rainkeeper/http_server.py b/src/rainkeeper/http_server.py new file mode 100644 index 0000000..2974024 --- /dev/null +++ b/src/rainkeeper/http_server.py @@ -0,0 +1,54 @@ +"""HTTP transport entry point for cia hosting, guarded by X-API-Key. + +Mirrors reed's ApiKeyMiddleware pattern (src/reed/mcp_server.py in the +reed repo): a pure-ASGI wrapper around FastMCP's own http_app(), since +the mounted MCP app is a Starlette sub-app rather than routes that can +take a Depends(). +""" +from __future__ import annotations + +import os +import secrets + +from starlette.responses import JSONResponse +from starlette.types import ASGIApp, Receive, Scope, Send + +from .server import mcp + +API_KEY = os.environ.get("RAINKEEPER_API_KEY", "") + + +class ApiKeyMiddleware: + def __init__(self, app: ASGIApp) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] != "http": + await self.app(scope, receive, send) + return + if API_KEY: + headers = dict(scope["headers"]) + key = headers.get(b"x-api-key", b"").decode() + if not secrets.compare_digest(key, API_KEY): + response = JSONResponse({"error": "Invalid API key"}, status_code=401) + await response(scope, receive, send) + return + await self.app(scope, receive, send) + + +app = ApiKeyMiddleware(mcp.http_app(path="/mcp")) + + +def main() -> None: + import uvicorn + + uvicorn.run( + app, + host=os.environ.get("RAINKEEPER_HOST", "0.0.0.0"), + port=int(os.environ.get("RAINKEEPER_PORT", "8001")), + log_level="info", + ) + + +if __name__ == "__main__": + main()