tmdb-mcp
AGENTS.md5 starsChanged 29 days agoMITView on GitHub
Single source of truth for working on this repository — for any model or agent. CLAUDE.md only references this file (@AGENTS.md); keep all shared guidance here, not in CLAUDE.md. (For end-user/runtime docs, see README.md.) A TypeScript MCP server for movie/TV data. Hybrid backend: TMDB is the backbone (search, metadata, people, trending) via its v3 REST API with a v4 Read Access Token; OMDb is optional enrichment supplying IMDb/Rotten Tomatoes/Metacritic ratings, keyed by the imdb_id TMDB returns. Design rationale (why two…
- Reads credentials
We split this AGENTS.md into the rules it gives an agent
8 rules. About 0 are common, 0 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 80
Keep clients fetch+cache only.
- Very specific: line 82
Every cached TmdbClient/OmdbClient method takes a trailing optional onStale?: () => void, forwarded down to TtlCache.wrapStaleOnError …
- Very specific: line 92
Every shaper in format.ts returns z.infer<typeof <name>Schema> (the matching schema from format.schemas.ts), built via that schema's …
- Very specific: line 105
Every schema in format.schemas.ts must reject unknown keys, via z.strictObject(...).
- Very specific: line 112
Never use z.date()/z.bigint()/z.nan()/.transform()/z.map()/z.set()/ z.symbol() in a tool's inputSchema or outputSchema (or anything they're …
- Very specific: line 127
Write tool descriptions and per-field .describe() text for the calling model: explain when to use a tool and what each parameter means.
- Very specific: line 140
Keep dependencies minimal.
- Very specific: line 158
Update CHANGELOG.md (Unreleased section) — see .agents/skills/changelog-style/SKILL.md for entry style.
≈ “Common” is an estimate: at least 1 in 5 of the 36,368 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md
Single source of truth for working on this repository — for any model or
agent. CLAUDE.md only references this file (@AGENTS.md); keep all shared
guidance here, not in CLAUDE.md. (For end-user/runtime docs, see README.md.)
Project shape
A TypeScript MCP server for movie/TV data. Hybrid backend: TMDB is the backbone
(search, metadata, people, trending) via its v3 REST API with a v4 Read Access
Token; OMDb is optional enrichment supplying IMDb/Rotten Tomatoes/Metacritic
ratings, keyed by the imdb_id TMDB returns. Design rationale (why two
clients, why not the IMDb API, TMDB endpoint quirks, template reuse) lives in
docs/architecture.md.
src/
index.ts # bin entry — calls start()
server.ts # buildServer() + start(); registers everything
config.ts # env → validated Config (zod)
version.ts # VERSION constant kept in sync with package.json by npm version
format.ts # raw TMDB/OMDb payloads → trimmed, agent-facing shapes
format.schemas.ts # Zod schemas mirroring format.ts's shapers 1:1; each tool's outputSchema
# AND the shaper itself parses its result through the matching schema
# before returning, so shaper/schema drift throws immediately
prompts.ts # MCP Prompts: multi-step plans that guide the model through the tools
lib/ # GENERIC carcass: http, rateLimit, cache, upstream, errors, logger, result
clients/ # tmdb.ts (backbone reads), omdb.ts (ratings enrichment)
tools/ # tmdb/ (search.ts, details.ts incl. OMDb enrichment, discover.ts, lookups.ts,
# fields.ts shared builders, index.ts composes registerTmdbTools), omdb.ts
# (get_ratings), shared.ts (READ_ONLY, requireConfigured(Cached) — try/catch →
# ToolResult)
__tests__/ # node:test (*.test.ts) + helpers.ts
scripts/ # build-tests.mjs, run-tests.mjs (generic), check-api.mjs (domain),
# sync-version.mjs (npm version hook), preversion-check.mjs (npm version
# gate — see .agents/skills/release/SKILL.md)
.agents/skills/ # reusable agent workflows for this repo (e.g. live-audit/) — plain Markdown
# with a YAML frontmatter name/description, not tied to any one tool's
# orchestration features. Same skill name/layout as this project's sibling
# MCP servers (steam-games-mcp, mal-mcp, anilist-mcp-server) — sync
# improvements both ways rather than letting them drift. `.claude/skills` is
# a symlink here (Codex CLI/Gemini CLI read `.agents/skills` directly,
# Claude Code the symlink)Commands
npm run build # tsc --noEmit + tsup → dist/index.js (single ESM bundle)
npm test # build tests with esbuild, run with node:test
npm run test:coverage # same, with coverage (gate: ~80%)
npm run lint # eslint
npm run format # prettier --write
npm run check:api # live upstream health-check (needs TMDB_API_TOKEN; OMDb check skipped without OMDB_API_KEY)
npm run inspector # run under the MCP InspectorConventions
- Docs and in-code text are English (README, docs, comments, tool descriptions, error messages).
- Runtime floor is Node ≥ 20.11 (global
fetch, stablenode:test,AbortSignal.any()); tsup targetsnode20. This is the floor for the shipped server. The dev toolchain needs Node ≥ 20.19 (eslint's ownengines), solintruns only on the Node 24 CI job while build/test run on the 20.11 floor too — don't raiseenginesto 20.19 to "fix" that mismatch, it would warn end users whose runtime is fine. - Log to stderr only — stdout is the MCP protocol channel. Use the logger; it redacts credentials.
- Tool failures return
{ isError: true }results (viarequireConfigured()/result.ts), never thrown — the agent should get an actionable message.requireConfigured(client, fn, validate?)'s optionalvalidatecallback is checked after the configured-check but beforefn— use it (instead of a manual duplicate configured-check) when a tool needs both checks in that specific order, e.g.get_ratingsintools/omdb.ts. - Mocked-
fetchtest fixtures must mirror the real upstream response shape for that exact endpoint, not just whatever fields make the current code pass — see .agents/skills/fixture-accuracy-check/SKILL.md. - Keep clients fetch+cache only; all raw→agent-facing shaping lives in
src/format.ts. Trim responses for token efficiency. - Every cached
TmdbClient/OmdbClientmethod takes a trailing optionalonStale?: () => void, forwarded down toTtlCache.wrapStaleOnError(lib/cache.ts) — it fires right before a stale (upstream-down) fallback value is returned. A tool handler that calls a cached method creates one viatools/shared.ts'strackStale()and passes.onStaleinto the client call plus.metaasrequireConfigured'sgetMeta, so a stale response surfaces as_meta: {"tmdb-mcp/stale": true}on the tool result (never insidestructuredContent— that stays pure domain shape, validated byoutputSchema). A new cached method that skips wiring this through will silently serve stale data with no way for the caller to detect it. - Every shaper in
format.tsreturnsz.infer<typeof <name>Schema>(the matching schema fromformat.schemas.ts), built via that schema's.parse()— notRecord<string, unknown>. Client methods inclients/*.tsmirror this precision (ReturnType<typeof shaperFn>/Page<...>) instead of widening back toRecord<string, unknown>; that widening is only correct at the actual wire-serialization boundary (tools/shared.ts'srequireConfigured()/lib/result.ts'sjsonResult()). Page<S>(format.ts) must stay atypealias, never aninterface: TS only synthesizes an index signature — required for assignability toRecord<string, unknown>, which thejsonResult()boundary above needs — on anonymous/aliased object types, never on named interfaces. Changing it to aninterfacecompiles fine in isolation but breaks every call site that passes aPage<...>through that boundary.- Every schema in
format.schemas.tsmust reject unknown keys, viaz.strictObject(...). A non-strict schema silently drops unknown keys instead of failing, which defeats the shaper/schema drift check above. Note (verified on Zod 4.4.3 and 4.6.5):.pick()/.extend()on astrictObjectpreserve its unknown-key rejection, so the derivedmovieCardSchema/tvCardSchemaare already strict; their trailing.strict()is defensive, not load-bearing — don't "reconcile" it away on the assumption pick/extend produce a loose shape. - Never use
z.date()/z.bigint()/z.nan()/.transform()/z.map()/z.set()/z.symbol()in a tool'sinputSchemaoroutputSchema(or anything they're built from, e.g.format.schemas.ts).@modelcontextprotocol/serverconverts every tool schema to JSON Schema viaz.toJSONSchema()with the defaultunrepresentable: "throw"— one of these types anywhere in the schema throws. Since SDK 2.3 that conversion is lazy, soregisterTool()itself succeeds and the throw moves to the firsttools/list;connectServer()in__tests__/helpers.tscallslistTools()up front, so the test suite still catches it.z.coerce.*is fine inconfig.ts(env-var parsing only; never reaches a tool schema). clients/tmdb.tsimport typesDiscoverParamsfromtools/tmdb/discover.ts(itsz.infersource of truth, alongside the hand-authored discover input schemas) — a lower-layer file type-importing from a higher-layer one. This is intentional and fully erased at build (no runtime circular import, verified viatsc/tsup); don't invert the import direction to "fix" it.- Write tool
descriptions and per-field.describe()text for the calling model: explain when to use a tool and what each parameter means. Check new or edited descriptions against thetool-description-checkskill (Glama's TDQS rubric) before committing. - Name a field for what it actually accepts, not a generic ID suffix —
this project's
id/idsfields are always a numeric TMDB id (resolve a title/name to one viasearch_movies/search_tv/search_peoplefirst), and a field that takes an external identifier in a different format says so in its own name (imdb_id, notid). Keep the same field name and Zod builder for the same concept across every tool that takes it (e.g.tmdbId,page,language,regionintools/tmdb/fields.ts) — grep sibling tools before naming a new field for an existing concept instead of defining an ad hoc one-off. - Keep dependencies minimal. New deps need a clear justification (supply-chain).
In particular, do not pull in a third-party TMDB SDK — the
lib/carcass already covers retries/cache/rate-limiting, and we shape responses ourselves. - Never commit secrets. Tokens come from env vars / OS keychain only.
- Cross-platform: macOS, Linux and Windows. Avoid POSIX-only shell in npm scripts (use the Node helper scripts).
- Commits: author/committer
Grinv <4070730+Grinv@users.noreply.github.com>; do not add aCo-Authored-Bytrailer.
Testing the live/published server
For a full audit of the currently published package — build/test/lint plus hammering the live MCP tools with edge cases, cross-checked against source — follow .agents/skills/live-audit/SKILL.md.
Before opening a PR
Run npm run build && npm test && npm run lint && npm run format:check.
Update CHANGELOG.md (Unreleased section) — see
.agents/skills/changelog-style/SKILL.md
for entry style.
Releasing
package.json is the single source of truth for the version; npm version
bumps + syncs every derived file + tags the release. See
.agents/skills/release/SKILL.md for the
full steps and MCP Registry details.
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 28,693 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository9 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- changelog-styleSkill
- docs-consistency-checkSkill
- fixture-accuracy-checkSkill
- live-auditSkill
- prompt-checkSkill
- releaseSkill
- self-learningSkill
- tool-description-checkSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

