Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .agents/skills/control-mediarss/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ C stop
Every command takes `--json`. `doctor`, `request`, `execute`, `browse`, and
`health` take `--origin <url>`; `dev` and `stop` take `--port`. `request` also
takes `--status`, `--not-contains`, `--header "k: v"`, `--body`, and
`--dump <file>` (binary bodies are summarized, not printed).
`--dump <file>` (binary bodies are summarized, not printed). `browse` also takes
`--headed` and `--out <dir>`. `health --published` also takes `--image <name>`
(default `kentcdodds/mediarss`).

## Environment

Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/control-mediarss/references/feature-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<token>/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` | |
8 changes: 3 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
35 changes: 11 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 4 additions & 3 deletions docs/mcp-server-best-practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 8 additions & 4 deletions docs/primitives.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions tools/feature-notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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,
},
Expand Down
Loading