diff --git a/.agents/skills/control-mediarss/SKILL.md b/.agents/skills/control-mediarss/SKILL.md index 3db7a68..667e776 100644 --- a/.agents/skills/control-mediarss/SKILL.md +++ b/.agents/skills/control-mediarss/SKILL.md @@ -35,7 +35,9 @@ C stop Every command takes `--json`. `doctor`, `request`, `execute`, `browse`, and `health` take `--origin `; `dev` and `stop` take `--port`. `request` also takes `--status`, `--not-contains`, `--header "k: v"`, `--body`, and -`--dump ` (binary bodies are summarized, not printed). +`--dump ` (binary bodies are summarized, not printed). `browse` also takes +`--headed` and `--out `. `health --published` also takes `--image ` +(default `kentcdodds/mediarss`). ## Environment diff --git a/.agents/skills/control-mediarss/references/feature-map.md b/.agents/skills/control-mediarss/references/feature-map.md index f436d10..715c70b 100644 --- a/.agents/skills/control-mediarss/references/feature-map.md +++ b/.agents/skills/control-mediarss/references/feature-map.md @@ -71,12 +71,12 @@ control environment (`npm run --silent control -- dev`). Commands are | Route | Pattern | Methods | What | Seed | Reach | High | | --- | --- | --- | --- | --- | --- | --- | -| `mcp` | `/mcp` | POST | MCP server (streamable HTTP); requires an OAuth bearer token | - | `request POST /mcp --status 401 (no token)` | yes | +| `mcp` | `/mcp` | GET, POST, OPTIONS | MCP server (streamable HTTP); requires an OAuth bearer token | - | `request POST /mcp --status 401 (no token)` | yes | | `mcpWidget` | `/mcp/widget/:token/*path` | GET | MCP UI widget media for a feed token | `execute --code-file tools/control-fixtures/curated-feed.ts` creates a curated feed with fixture audio and prints its id and token | `request GET /mcp/widget//audio/Inkheart.m4b` | yes | ## Operations | Route | Pattern | Methods | What | Seed | Reach | High | | --- | --- | --- | --- | --- | --- | --- | -| `health` | `/health` | GET | Public health snapshot: status, version, commit SHA, database, media roots, MCP protocol | - | `health (or request GET /health --contains '"status":"ok"')` | | +| `health` | `/health` | GET | Public health snapshot: status, version, commit SHA, database, media roots, MCP, OAuth | - | `health (or request GET /health --contains '"status":"ok"')` | | | `adminHealth` | `/admin/health` | GET | Admin health snapshot (same data as /health) | - | `request GET /admin/health --contains status` | | diff --git a/AGENTS.md b/AGENTS.md index de59c07..c44d2a2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,11 +6,9 @@ media are public by token, and the admin UI sits behind Cloudflare Access. Node.js + npm, Remix 3 (`remix/component`, no React), SQLite via `remix/data-table`. -`npm run validate` is the single local gate. CI runs that command, Control CLI -smoke + Playwright, and a Docker image build and `/health` smoke as independent -jobs. Git hooks (installed by `npm install`) run format and lint checks on -commit and typecheck on push. Run `npm run check` to auto-fix format and lint -before you finish. +`npm run validate` is the single local gate. Run `npm run check` to auto-fix +format and lint before you finish. CI jobs, git hooks, and the data/test setup +are in [docs/contributing.md](docs/contributing.md). This file is a map. Open the page that owns the task: diff --git a/README.md b/README.md index dd47176..b1bd02a 100644 --- a/README.md +++ b/README.md @@ -14,27 +14,11 @@ ## Development -- Runtime: Node.js 24 (`engines.node` is `>=24.12.0`) -- Package manager: npm -- UI: Remix 3 (`remix@3.0.0`, `remix/component`, no React) -- Linter: `oxlint` -- Formatter: `oxfmt` (shared preset from `@epic-web/config`) - -Installed Remix docs live at `node_modules/remix/INDEX.md`. The repo skill is -`.agents/skills/remix/SKILL.md`. How to work in the repo is in -[docs/contributing.md](docs/contributing.md). - -Common commands: - -- `npm run dev` -- `npm run lint` -- `npm run format` -- `npm run validate` - -SQLite is `remix/data-table` on `node:sqlite`. Tables are in `app/db/schema.ts`; -SQL migrations are in `app/db/migrations/`. The server applies pending -migrations at startup. CRUD helpers, test databases, and `npx remix db` are in -[docs/contributing.md](docs/contributing.md#data-layer). +Node.js 24 (`engines.node` is `>=24.12.0`), npm, Remix 3 (`remix@3.0.0`, +`remix/component`, no React). Format with `oxfmt`, lint with `oxlint`. How to +work in the repo, including `npm run validate`, the data layer, and +`npx remix db`, is in [docs/contributing.md](docs/contributing.md). Installed +Remix docs live at `node_modules/remix/INDEX.md`. ## Screenshots @@ -301,6 +285,9 @@ The following environment variables can be configured: alias). - `ALLOWED_HOSTS`: Optional comma-separated hosts allowed on `/oauth/token`. Unset means any `Host` header is accepted. +- `MEDIA_ANALYTICS_MAX_SQLITE_VARIABLE_NUMBER`: Optional cap on SQLite IN-clause + bind variables when aggregating per-file analytics. Unset uses the compiled + SQLite `MAX_VARIABLE_NUMBER` (or `999`). The Docker image sets `DATABASE_PATH=/data/sqlite.db` and `CACHE_DATABASE_PATH=/data/cache.db`. The `./data/…` defaults above apply to @@ -319,9 +306,9 @@ MediaRSS routes fall into five groups: `/mcp` off the Access policy so clients can connect after you approve them. - **OAuth discovery and tokens** (`/.well-known/*`, `/oauth/*`) — public so clients can register and redeem codes. Consent itself is `/admin/authorize`. -- **Health** (`/health`) — public snapshot (status, version, commit SHA, - database, media roots, MCP protocol). Not rate-limited unless `?probe=` is - present (`?probe=cimd` live-fetches Kody's client metadata). +- **Health** (`/health`) — public JSON snapshot of process, database, media + roots, MCP, and OAuth. Not rate-limited unless `?probe=` is present + (`?probe=cimd` live-fetches Kody's client metadata). Podcast apps need unauthenticated token URLs, so use **Cloudflare Tunnel** and an Access policy on `/admin` rather than app-level login. diff --git a/docs/contributing.md b/docs/contributing.md index 81f0dbb..a6695fb 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -74,10 +74,10 @@ tools, resources, or prompts. CLI. - Database tests use `createTestDatabase()` from `app/db/test-database.ts` (in-memory, migrated). Module-level code that uses the shared `db` runs - against the throwaway `DATABASE_PATH` from `test/global-setup.ts` (an empty - file until the test migrates it). Route and handler tests that need tables - `await migrateDatabase(db)` at module load. Vitest runs one worker so those - tests don't race. + against the throwaway `DATABASE_PATH` from `test/global-setup.ts` (the file is + created and WAL-enabled on import; unmigrated until the test migrates it). + Route and handler tests that need tables `await migrateDatabase(db)` at module + load. Vitest runs one worker so those tests don't race. ## Testing diff --git a/docs/mcp-server-best-practices.md b/docs/mcp-server-best-practices.md index da9775b..06ab1de 100644 --- a/docs/mcp-server-best-practices.md +++ b/docs/mcp-server-best-practices.md @@ -4,9 +4,10 @@ How MediaRSS's MCP server is built. Change `app/mcp/` to match these conventions. The server accepts Streamable HTTP revision `2026-07-28` only -(`createMcpHandler` from `@modelcontextprotocol/server`). Clients authenticate -with OAuth (PKCE); tools, resources, and prompts are registered from the -caller's scopes. +(`createMcpHandler` from `@modelcontextprotocol/server`, `legacy: 'reject'`). +Clients authenticate with OAuth (PKCE); tools, resources, and prompts are +registered from the caller's scopes. CORS advertises `GET`, `POST`, and +`OPTIONS`; legacy session `DELETE` is rejected. ## Layout diff --git a/docs/primitives.md b/docs/primitives.md index 832dd17..2041196 100644 --- a/docs/primitives.md +++ b/docs/primitives.md @@ -71,11 +71,15 @@ concern; extend it instead of duplicating it. - `app/client/admin/`: the admin SPA (`entry.tsx`, `app-root.tsx` holds the page registry, `admin-pages.ts` matches URLs; `FeedDetail` and `MediaDetail` each - serve their view and edit routes; `url-state.ts` for URL-synced state). - `app/helpers/feed-list-sort.ts` and `media-list-sort.ts` own the admin list - sort keys. + serve their view and edit routes; `edit-route-paths.ts` builds those hrefs; + `url-state.ts` for URL-synced state). `app/helpers/feed-list-sort.ts` and + `media-list-sort.ts` own the admin list sort keys. `app/helpers/format.ts` + formats dates, durations, and sizes. +- `app/assets.ts`: the `remix/assets` server, script entries, and the + `remove-accents` import-map shim. - `app/client/widgets/`: MCP UI widgets. -- `app/components/`, `app/styles/`: shared components and style tokens. +- `app/components/`, `app/styles/tokens.ts`: shared components and style tokens. + CSS variables live in `app/assets/styles.css`. ## Ops and tooling diff --git a/tools/feature-notes.ts b/tools/feature-notes.ts index 968ed11..59ee201 100644 --- a/tools/feature-notes.ts +++ b/tools/feature-notes.ts @@ -36,7 +36,7 @@ export const featureNotes = { health: { surface: 'ops', summary: - 'Public health snapshot: status, version, commit SHA, database, media roots, MCP protocol', + 'Public health snapshot: status, version, commit SHA, database, media roots, MCP, OAuth', reach: 'health (or request GET /health --contains \'"status":"ok"\')', }, feed: { @@ -99,7 +99,7 @@ export const featureNotes = { mcp: { surface: 'mcp', summary: 'MCP server (streamable HTTP); requires an OAuth bearer token', - methods: 'POST', + methods: 'GET, POST, OPTIONS', reach: 'request POST /mcp --status 401 (no token)', high: true, },