feat(mcp): add read-only API server foundation #76
Closed
timothy
wants to merge 5 commits from
issue58-mcp into main
pull from: issue58-mcp
merge into: :main
:main
:renovate/meziantou.analyzer-3.x
:renovate/nuget-sixlabors.imagesharp-vulnerability
:913-scripted-playout-401
:929-vuln-pkcs-imagesharp-v4
:renovate/dotnet-monorepo
:release/v26.15.0-notes
:renovate/lucene.net
:renovate/cliwrap-3.x
:issue-806-guard-populations
:scratch/767b-poisoned
:scratch/767b-control
:release/v26.14.0-notes
:release/v26.14.0
:renovate/sqlitepclraw.bundle_e_sqlite3-3.x
:docs/510-skill-logo-bug-policy
:fix/510-watermark-resolution-policy
:fix/629-verdict-classifier-falseopens
:fix/609-decisions-edit-token-scope
:issue-135-clear-to-none
:release/v26.12.0-notes
:fix/409b-lastscan-api-parity
:fix/401-updatechannel-mirror-422
:fix/327-playlist-rename-validation
:fix/410-scancancel-log-level
:fix/409-447-librariesscreen-neverscanned
:fix/338-zap-exit-code
:fix/367-plex-budget-message
:fix/310-debom-legacy-cs
:ci/604-lane-rebalance
:feat/388-design-mirror
:feat/247-test-ownership
:feat/247-primary-action
:feat/357-player-owned-playback
:feat/357-jellyfin-plugin-poc
:fix/289-mcp-hardening
:issue58-mcp
:feat/244-channels-extract
:ci/auto-bump-prod-compose
:feat/multi-rerun-collections-api
:feat/collections-api
:feat/quick-wins
:feat/185-docs-part2
:feat/140-collections-screen
:feat/146-channel-edit
:feat/147-classic-ui-link
:issue22-renovate-dashboard
:feat/91-cutover
:feat/63-composite-create
:feat/65-library-browse
:feat/85-epg
:feat/86-schedule-editor
:feat/109-dashboard-data
:feat/99-session-tracking
:fix/dockerfile-node-tag
:feat/59-spa-foundation
:docs/59-ui-redesign-brief
:feat/102-json-guide
:feat/111-schedule-durations
:feat/104-artwork-upload
:feat/103-media-sources-api
:feat/playouts-read-api
:feat/108-health-api
:feat/105-picker-list-endpoints
:issue-97-channel-state-api
:issue42-jellyfin-musicvideos
:issue46-rest-api-error-contract
:dependabot/nuget/ErsatzTV.FFmpeg.Tests/multi-d307a2e06f
:qsv-improvements
:hdr-vulkan-cuda-test
No Reviewers
Labels
Clear labels
ad-hoc
api
bug
ci-cd
content
dependencies
enhancement
frontend
in-progress
jellyfin
parked
priority: high
priority: low
priority: medium
review
security
One-off / ad-hoc work not tracked by a dedicated issue
REST API / HTTP endpoints
Something isn't working
Build, test, deploy pipeline
Channel content / schedules / playlists
Dependency updates (Renovate)
New feature or improvement
ChicoryTV React SPA frontend
Claimed by an active session — do not pick up
Jellyfin tuner / IPTV integration
Excluded from automatic queue pickup; work only when explicitly selected
Adversarial review finding
Security / vulnerability fix
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: timothy/ersatztv#76
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
ErsatzTV.Mcp, a small stdio JSON-RPC MCP server over the current stable REST API.X-Api-Keybehavior, and deferred #63-#68 redesign workflows indocs/mcp.md.Verification
TZ=UTC dotnet restore ErsatzTV.sln -v minimalTZ=UTC dotnet build ErsatzTV.sln --no-restore -v minimalTZ=UTC dotnet test ErsatzTV.Mcp.Tests/ErsatzTV.Mcp.Tests.csproj -v minimalTZ=UTC dotnet test ErsatzTV.sln --no-build -v minimalDeferred
Refs #58
190106237fto945d108334Non-blocking polish from the fix-commit re-review (verdict MERGEABLE): - BuildPath: comment the assumption that each {param} is its own path segment (revalidate the whole path if a template ever concatenates adjacent params). - Two additive BoundedLineReader tests: exactly-at-cap line returned intact (inclusive max), and \r\n carriage-return stripping. No logic change. 36 tests (was 34). Refs #289 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Folded the #197 MCP-consumer security hardening (#289) into this branch —
d1c04030..65d88b51(3 commits, fast-forward):ERSATZTV_ALLOW_WRITES, default false) enforced in the executor — the runtime backstop behind the read-only catalog, forward-compatible with future write tools.-32700/-32600/-32603responses).InputSchema; bounded stdin reader;./..path rejection; reverse-proxy prefix preservation; env clamps.docs/mcp.mdupdated, incl. thatERSATZTV_API_KEYis now effectively required (Bundle A onmaingates all/apireads behindX-Api-Key).Reviews: 2 cold adversarial passes + 1 Codex pass on the initial diff (complementary catches), 1 cold re-review of the fix commit = MERGEABLE. 36 tests (was 12), 0 warnings, format-clean.
⚠️ This branch is ~5 days behind
main(pre-Bundle-A/B, pre-#91b). Before this PR merges: mergemainin, regenerate any generated artifacts, and run the full-solution CI. MCP go-live is also gated on #197 (arc item 5). See #289 for the full close-out.claiming — session started 2026-07-12 (night). Finalizing this branch for the go-live main-merge (owner-directed as the first #197 frontier step, ahead of #286 versioning).
Plan: rebase
issue58-mcponto currentorigin/main(branch is 427 commits behind — pre-Bundle-A auth, pre-#91b), resolve conflicts (expectErsatzTV.sln/docs/README.md/docs/decisions.md), regen any generated artifacts, full-solution build+test, live-verify the MCP against current main's/apisurface (Bundle-AX-Api-Keygating is new since this branch was written), independent re-review of the rebase delta, then merge.#286(/api/v1route sweep) is sequenced immediately after this lands.Superseded — closing without merge (owner-directed 2026-07-12)
Decision: not merging this branch. It's the v0 read-only foundation, written 427 commits / ~5 days ago against a pre-Bundle-A, pre-#91b API. Rather than rebase 427 commits and then immediately route-sweep it in #286 (
/api/v1versioning, the next #197 frontier item, which rewrites every route), we'll rebuild the MCP fresh under the existing epic #58 — read + cautious-write CRUD — against the frozen/api/v1contract once #286 lands.Why fresh rather than expand-in-place:
/apiwrite surface has matured enormously since this branch (full ChicoryTV CRUD over channels/collections/schedules/playouts/media-sources, auth Bundle A/B, ETag/If-Match optimistic concurrency, the #253/#265 replace-all contract) → a read+write MCP is now viable, so the read-only-only framing is outdated.Not lost: the branch
issue58-mcpstays as reference (not deleting it), and the #289 hardening (runtime read-only posture /ERSATZTV_ALLOW_WRITES, JSON-DoS guards, per-request timeout, bounded stdin, arg validation, injection-framing docs) is the security baseline the fresh build must carry forward. The v0 plan doc +docs/mcp.mdremain in history.Re-scoping #58 accordingly.
Pull request closed