# Media MCP surface The media archive is designed for agent access. The daemon exposes one service, and the MCP tools are thin wrappers around it. The same service backs the Web UI routes, so the two clients cannot drift. Status: implemented (C8). The tools are registered in `poc/src/control-bridge.ts`; read tools are also in `run_commands`. Advanced `query_media` filters (author, date, text) are planned; the index has the columns. ## Service interface ```ts interface MediaService { startScrape(input: { channelId: string; mode: "links" | "download"; backend?: "auto" | "slopbot" | "puppet" }): Promise; getScrape(scrapeId: string): Promise; listScrapes(): Promise; stopScrape(scrapeId: string): Promise; deleteScrape(scrapeId: string): Promise; queryMedia(query: MediaQuery): Promise<{ items: MediaItem[]; nextCursor: string | null }>; getMediaItem(id: string): Promise; storageStatus(): Promise; } ``` `MediaQuery` (implemented): `kind`, `source`, `scrapeId`, `channelId`, `authorId`, `aspect`, `resolution`, `after`, `before`, `limit`, `offset`. The `aspect` and `resolution` values are the preset keys from `filter-presets.ts`; `after`/`before` are ISO times on `posted_at`. Planned: `guildId`, `text`. `StorageStatus`: `usedBytes`, `limitBytes`, `warnPercent`, `overrunPercent`, `byKind`, `scrapes: [{ id, bytes, state, createdAt }]`. ## Tools | Tool | Access | Purpose | | --- | --- | --- | | `scrape_media` | trusted | Start a scrape (`channelId`, `guildId?`, `mode?`, `backend?`, `downloadDir?`) | | `list_media_scrapes` | trusted | Every scrape with state, size, and counts | | `get_media_scrape` | trusted | One scrape, live counters and cursor | | `stop_media_scrape` | trusted | Stop the active scrape | | `query_media` | trusted | Filter and page through media items | | `get_media_item` | trusted | One item with sources and local path | | `get_media_storage` | trusted | Usage against the limit, per-kind breakdown | | `delete_media_scrape` | admin | Delete a scrape and its files | Read tools are cache-only against `index.db` and the scrape registry. They never call Discord. `scrape_media` and `stop_media_scrape` drive the same job queue as the chat command, so pacing and the action log apply. ## Rules - Every tool goes through the existing trust tiers. `delete_media_scrape` and `mode: "download"` need `admin`. - Tool calls are written to the action log like any other daemon action. - The storage limit is enforced for the agent exactly as for the chat command: near the limit, `scrape_media` returns the warning and needs a second call with `confirm: true`. - Media bytes are never returned inline. Tools return metadata, the original URL, and the local path; an agent that wants the bytes reads the file through the daemon host. ## Hooks to leave in place before C8 1. `MediaService` is constructed once in the daemon and passed to both the Web UI routes and the MCP registration. No globals. 2. `ScrapeJob` and `MediaItem` are plain JSON-serializable objects, the same shape the Web UI consumes. 3. The job queue exposes `start`, `stop`, `list`, and `events` so MCP and the UI can subscribe to the same stream. 4. Storage accounting is a service method, not UI code, so the MCP limit check uses the same numbers as the Settings bar.