mcp-metacritic 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,21 +6,29 @@
6
6
  [![MCP Registry](https://img.shields.io/badge/MCP_Registry-listed-6E56CF)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.smeet666/mcp-metacritic)
7
7
  [![Glama](https://glama.ai/mcp/servers/smeet666/mcp-metacritic/badges/score.svg)](https://glama.ai/mcp/servers/smeet666/mcp-metacritic)
8
8
  [![M8ven](https://m8ven.ai/badge/mcp/smeet666-mcp-metacritic-1yvblv?variant=verified)](https://m8ven.ai/mcp/smeet666-mcp-metacritic-1yvblv)
9
+ [![LobeHub](https://lobehub.com/badge/mcp/smeet666-mcp-metacritic)](https://lobehub.com/mcp/smeet666-mcp-metacritic)
9
10
  [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=metacritic&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1tZXRhY3JpdGljIl19)
10
11
  [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=metacritic&config=%7B%22name%22%3A%22metacritic%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-metacritic%22%5D%7D)
12
+
11
13
  <!-- m8ven-verify: 01151bc5a8514e55175f063339bedc6f -->
12
14
 
13
- An [MCP](https://modelcontextprotocol.io) server for
14
- [Metacritic](https://www.metacritic.com). Search films, shows and games, read
15
- Metascores and audience scores, and quote what individual critics wrote, with
16
- the publication named and the article linked. **No API key, no account, no
17
- configuration.**
15
+ [Metacritic](https://www.metacritic.com) gathers what critics and audiences said
16
+ about films, television series and video games. Each entry carries the year, the
17
+ age rating, the genres, and two scores of its own: the Metascore, a weighted
18
+ average of the professional reviews, and the user score, out of ten, from the
19
+ people who signed up to rate it. Under each entry sit the reviews themselves,
20
+ with the publication that ran them and the quoted line.
21
+
22
+ This server connects a chat client to that catalogue. You can search for a title,
23
+ read its entry with its scores and its details, browse a catalogue by score,
24
+ recency or popularity, and read the reviews of one title, filtered by critic or
25
+ audience and by how favourable they were. It needs no API key and no account.
18
26
 
19
- _(Version française plus bas / [French version below](#mcp-metacritic-français))_
27
+ _[Version française](#mcp-metacritic-français)_
20
28
 
21
29
  ---
22
30
 
23
- ## Quickstart
31
+ ## Install
24
32
 
25
33
  **One-click install**
26
34
 
@@ -46,6 +54,8 @@ claude mcp add metacritic -- npx -y mcp-metacritic
46
54
  }
47
55
  ```
48
56
 
57
+ Node 24 or later is required, and no environment variable has to be set.
58
+
49
59
  ### With Docker
50
60
 
51
61
  ```json
@@ -53,168 +63,220 @@ claude mcp add metacritic -- npx -y mcp-metacritic
53
63
  "mcpServers": {
54
64
  "metacritic": {
55
65
  "command": "docker",
56
- "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-metacritic:2.0.0"]
66
+ "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-metacritic:2.1.0"]
57
67
  }
58
68
  }
59
69
  }
60
70
  ```
61
71
 
62
- `-i` keeps stdin open, which is where the protocol travels, and no `-t` is
63
- passed: a TTY rewrites the stream and breaks it. The container needs outbound
64
- HTTPS to `www.metacritic.com` and `backend.metacritic.com`, and nothing else: no volume, no port, no environment variable, no credential.
72
+ `-i` keeps stdin open, which is where the protocol travels, and `-t` is left out
73
+ because a TTY rewrites the stream. The container needs outbound HTTPS to
74
+ `backend.metacritic.com`, and nothing else: no volume, no port, no credential.
65
75
 
66
- **Bundle, without npm**
76
+ ### Bundle, without npm
67
77
 
68
- Download `mcp-metacritic-<version>.mcpb` from
69
- [the latest release](https://github.com/smeet666/mcp-metacritic/releases/latest) and open
70
- it. A client that supports MCP bundles installs it on its own, with no npm and
71
- no configuration file to edit. The bundle carries its dependencies, so nothing
72
- is fetched at install time.
78
+ Download `mcp-metacritic-2.1.0.mcpb` from
79
+ [the latest release](https://github.com/smeet666/mcp-metacritic/releases/latest)
80
+ and open it. A client that supports MCP bundles installs it on its own, with no
81
+ npm and no configuration file to edit. The bundle carries its dependencies, so
82
+ nothing is fetched at install time.
73
83
 
74
- ## Tools
84
+ ## What you can ask
85
+
86
+ - "What did critics make of The Matrix?"
87
+ - "Read me a few negative reviews of that game."
88
+ - "What are the best-reviewed horror films?"
89
+ - "How does the user score compare to the Metascore?"
90
+ - "What came out recently that reviewed well?"
75
91
 
76
- | Tool | What it does | Key parameters |
77
- | --------------- | ----------------------------------------------------------- | ------------------------------------------------- |
78
- | `search_titles` | Finds films, shows and games by title. Compact rows. | `query`, `kind`, `limit` |
79
- | `get_title` | Reads one entry, section by section. | `slug`, `kind`, `sections`, `max_chars`, `offset` |
80
- | `get_reviews` | Individual critic or audience reviews, filtered by verdict. | `slug`, `kind`, `source`, `sentiment`, `limit` |
81
- | `browse_titles` | Rankings: best rated, newest, most looked at. | `kind`, `sort`, `genre`, `limit`, `offset` |
92
+ The ordinary path runs from a search to an entry: a row carries a `slug` and a
93
+ `kind`, and `get_title` and `get_reviews` take both together.
82
94
 
83
- Search returns a `slug` and a `kind` for every result, and the other tools take
84
- both. That is the intended chain: search, then read.
95
+ ## Tools
85
96
 
86
- The server is **read-only**. It writes nothing back to Metacritic.
97
+ | Tool | What it does |
98
+ | --------------- | ----------------------------------------------------------- |
99
+ | `search_titles` | Finds films, series and games by title. |
100
+ | `get_title` | Reads one entry, its scores and its details. |
101
+ | `get_reviews` | Reads the reviews of one entry, by source and by sentiment. |
102
+ | `browse_titles` | Lists a catalogue by score, recency or popularity. |
103
+
104
+ A title is addressed by its `slug` together with its `kind`, since the same slug
105
+ can name a film and a game.
106
+
107
+ ### `search_titles`
108
+
109
+ Finds films, series and games by title.
110
+
111
+ | Argument | Type | Required | What it does |
112
+ | -------- | ----------------------------------------------- | -------- | -------------------------- |
113
+ | `query` | string, at least 1 character | yes | A title, or part of one. |
114
+ | `kind` | `movie`, `show`, `game` or `any`, default `any` | no | Which catalogue to search. |
115
+ | `limit` | integer, 1 to 50, default `10` | no | Rows to serve. |
116
+
117
+ **In return:** rows carrying `slug` and `kind`, which `get_title` and
118
+ `get_reviews` take together; `title`; `year`; `release_date`; `rating`, the age
119
+ rating as published; `metascore`; `user_score`, which the search route publishes
120
+ for no row and which is null on every one of them; and `source_url`. **A score the
121
+ site has not computed is `null`, never `0`:** on a scale that starts at zero the
122
+ two would be indistinguishable, and a title with too few reviews carries none.
123
+
124
+ ### `get_title`
125
+
126
+ Reads one entry. The heavier parts are asked for rather than served by default,
127
+ and each one beyond the default costs a request.
128
+
129
+ | Argument | Type | Required | What it does |
130
+ | ----------- | --------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------- |
131
+ | `slug` | string, at least 1 character | yes | The identifier a row carries. |
132
+ | `kind` | `movie`, `show` or `game` | yes | Which catalogue it belongs to. |
133
+ | `sections` | array of `basic`, `scores`, `awards`, `production`, `networks`, `where_to_watch`, default `["basic", "scores"]` | no | Which parts to return. |
134
+ | `max_chars` | integer, 200 to 20000, default `4000` | no | How much of the description to serve. |
135
+ | `offset` | integer, 0 or more, default `0` | no | Where to resume the description. |
136
+
137
+ **In return:** the entry a search row carries, plus `description`, `tagline`,
138
+ `genres`, `duration_minutes` and `imdb_id`, each `null` where the page states
139
+ nothing. `total_chars`, `returned_chars` and `offset` describe the slice of the
140
+ description served.
141
+
142
+ ### `get_reviews`
143
+
144
+ Reads the reviews of one entry.
145
+
146
+ | Argument | Type | Required | What it does |
147
+ | ----------- | --------------------------------------------------------- | -------- | ---------------------------------- |
148
+ | `slug` | string, at least 1 character | yes | The identifier a row carries. |
149
+ | `kind` | `movie`, `show` or `game` | yes | Which catalogue it belongs to. |
150
+ | `source` | `critic` or `user`, default `critic` | no | Whose reviews to read. |
151
+ | `sentiment` | `all`, `positive`, `neutral` or `negative`, default `all` | no | How favourable a review has to be. |
152
+ | `limit` | integer, 1 to 50, default `10` | no | Reviews to serve. |
153
+ | `offset` | integer, 0 or more, default `0` | no | Reviews to skip, for paging. |
154
+
155
+ **In return:** `reviews`, each with its `quote` as published, its `score`, the
156
+ `max` that score is out of, which is 100 for a critic and 10 for a user, and the
157
+ `publication` that ran it. **Name the publication when quoting a review.**
158
+ `total_available` counts every review Metacritic holds from that source for the
159
+ entry, whatever `sentiment` was asked for, since the sentiment sorts the sample
160
+ this server received rather than the request that fetched it. `next_offset`
161
+ continues.
162
+
163
+ ### `browse_titles`
164
+
165
+ Lists a catalogue.
166
+
167
+ | Argument | Type | Required | What it does |
168
+ | -------- | ----------------------------------------------- | -------- | -------------------------------------- |
169
+ | `kind` | `movie`, `show` or `game`, default `movie` | no | Which catalogue to list. |
170
+ | `sort` | `score`, `recent` or `popular`, default `score` | no | How the rows are ordered. |
171
+ | `genre` | string | no | A single genre name, such as `Horror`. |
172
+ | `limit` | integer, 1 to 50, default `20` | no | Rows to serve. |
173
+ | `offset` | integer, 0 or more, default `0` | no | Rows to skip, for paging. |
174
+
175
+ **In return:** the rows `search_titles` returns, with `total_available`,
176
+ `offset`, `next_offset` and the `kind`, `sort` and `genre` the listing was read
177
+ under.
178
+
179
+ ## Two scores, two things measured
180
+
181
+ The Metascore is a weighted average of professional reviews, out of 100. The user
182
+ score is the average of what registered members gave, out of 10. They measure
183
+ different populations on different scales, and a title can carry one and not the
184
+ other. Read each with the `max` its reviews state, and report a missing score as
185
+ missing.
87
186
 
88
- ### Things worth knowing
187
+ ## Configuration
89
188
 
90
- **The two scores are on different scales.** The critic Metascore runs to 100 and
91
- the audience score to 10, so 73 and 8.9 describe similar opinions. Every score
92
- this server returns carries its own `max`, and the tool descriptions say so,
93
- because averaging the two is the single easiest mistake to make with this data.
189
+ Every variable is optional. Set them in the `env` block of your client config.
94
190
 
95
- **Sections are opt-in because an entry is large.** A detail response is 46 KB
96
- for a film, most of it artwork variants. `get_title` returns `basic` and
97
- `scores` by default; `awards`, `production`, `networks` and `where_to_watch`
98
- cost an extra request each and are only fetched when asked for.
191
+ | Variable | Default | What it does |
192
+ | ------------------------ | -------------------- | ---------------------------------------------------------------------------------- |
193
+ | `MC_USER_AGENT` | the project identity | Names your application to the site, with an address where a person can be reached. |
194
+ | `MC_MIN_INTERVAL_MS` | `1000` | Gap between two requests, from 500 to 60000. |
195
+ | `MC_TIMEOUT_MS` | `15000` | Deadline for one request, from 1000 to 120000. |
196
+ | `MC_MAX_RETRIES` | `3` | Attempts after a transient failure, from 0 to 10. |
197
+ | `MC_CACHE_TTL_MS` | `86400000` | How long a catalogue entry stays in memory, from 0 to 604800000. |
198
+ | `MC_SCORES_CACHE_TTL_MS` | `3600000` | How long scores and reviews stay in memory, from 0 to 86400000. |
199
+ | `MC_CACHE_MAX_ENTRIES` | `200` | Answers held in memory at once, from 0 to 10000. |
200
+ | `MC_LOG_LEVEL` | `error` | `silent`, `error`, `info` or `debug`, written to stderr. |
99
201
 
100
- **Reviews are a sample, not a page.** Metacritic counts 36 critic reviews on a
101
- given film and serves 7 through this route, whatever paging you ask for. The
102
- tool says so rather than presenting the sample as the whole list, and links the
103
- entry page where the rest can be read.
202
+ Scores move as reviews come in, especially around a release, so they are held for
203
+ an hour where a catalogue entry is held for a day. A value outside its range
204
+ falls back to the default, and the reason is written to stderr.
104
205
 
105
- **A failure is never an empty result.** Every response carries either a `data`
106
- object or an `errors` array, and this server reads that distinction rather than
107
- guessing. A missing entry produces an error with a code; a request that could
108
- not be made says it could not be made, and never that Metacritic publishes
109
- nothing. Those are very different answers and a model cannot tell them apart on
110
- its own.
206
+ ## Errors
111
207
 
112
- **Search matches titles, and nothing else.** It cannot find an entry from a
113
- plot detail, a person or a studio, it cannot page, and for a multi-word query
114
- the site counts matches loosely: "the matrix" reports over 55 000 entries
115
- because it counts anything matching either word.
208
+ Every failure carries one of six codes, a message, and where it helps a hint
209
+ naming the next move.
116
210
 
117
- **Browse paging is approximate.** Metacritic does not order tied entries
118
- stably, so an entry can appear on two consecutive pages. Deduplicate by slug
119
- rather than counting rows.
211
+ | Code | What happened | What to do |
212
+ | --------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
213
+ | `not_found` | The site answered, and holds no such entry. | Check the slug and the kind with `search_titles`. |
214
+ | `invalid_input` | The arguments were refused before any request went out. | Read the message, which names the argument. |
215
+ | `rate_limited` | The site asked this client to slow down. | Wait the number of seconds the hint names and call again with the same arguments. The entry is still there. |
216
+ | `parse_failure` | The answer arrived in a shape this client cannot read. | Report it at [the issue tracker](https://github.com/smeet666/mcp-metacritic/issues). |
217
+ | `network_error` | The request did not complete. | Try again shortly. |
218
+ | `timeout` | The request passed its deadline. | Raise `MC_TIMEOUT_MS`, or ask for fewer rows. |
120
219
 
121
- ## Configuration
220
+ ## As a library
122
221
 
123
- Every variable is optional. Set them in the `env` block of your MCP client config.
222
+ The layer reading the site is published on its own, with its pacing, its cache
223
+ and its errors, and with no protocol attached.
124
224
 
125
- | Variable | Default | Purpose |
126
- | ------------------------ | ------------------------------------ | -------------------------------------------------------------- |
127
- | `MC_USER_AGENT` | `mcp-metacritic v<version> (<repo>)` | User-Agent sent to Metacritic. |
128
- | `MC_MIN_INTERVAL_MS` | `1000` | Minimum gap between requests. Values below 500 ms are ignored. |
129
- | `MC_TIMEOUT_MS` | `15000` | Per-request timeout. |
130
- | `MC_MAX_RETRIES` | `3` | Retries on refusals and transient errors. |
131
- | `MC_CACHE_TTL_MS` | `86400000` | Catalogue cache lifetime (24 hours). |
132
- | `MC_SCORES_CACHE_TTL_MS` | `3600000` | Scores and reviews cache lifetime (1 hour). |
133
- | `MC_CACHE_MAX_ENTRIES` | `200` | In-memory cache size. |
134
- | `MC_LOG_LEVEL` | `error` | `silent`, `error`, `info` or `debug`. Logs go to stderr. |
225
+ ```ts
226
+ import { McClient } from "mcp-metacritic/client";
135
227
 
136
- A User-Agent claiming to be a browser has the project's own identity appended
137
- to it, so the traffic stays attributable whatever a caller sets.
228
+ const client = new McClient();
229
+ const { data, cached } = await client.getDetail("movie", "the-matrix");
230
+ console.log(data.title, data.metascore, cached);
231
+ ```
138
232
 
139
- ## How it works
233
+ Each read answers `{ data, cached }`, and throws an error carrying one of the six
234
+ codes. The floor between two requests holds here as well.
140
235
 
141
- Metacritic's website is served by a JSON backend at
142
- `backend.metacritic.com`, and this server calls the same routes its own pages
143
- call. It sends one request at a time, paces itself, backs off when refused, and
144
- keeps two in-memory caches: a day for catalogue entries, which change when
145
- someone edits them, and an hour for scores and reviews, which move as reviews
146
- land.
236
+ ## Pacing and attribution
147
237
 
148
- Streaming links arrive wrapped in click-tracking redirects of around 800
149
- characters, with the real destination inside. This server unwraps them: you get
150
- the provider's own URL, not a tracker.
238
+ Requests go out one at a time with at least a second between them, and the floor
239
+ of half a second holds however the server is configured. The `User-Agent` always
240
+ ends with the project identity and an address where a person can be reached.
151
241
 
152
- ### On the terms of use
242
+ Every result carries the address of the Metacritic page, and every quoted review
243
+ carries the publication that ran it. The reviews belong to their authors and to
244
+ the publications that ran them.
153
245
 
154
- `metacritic.com/terms-of-use/` returns 404, so the terms could not be read.
155
- This is worth stating plainly rather than leaving implicit.
246
+ This MCP server is an unofficial project, with no affiliation to Metacritic.
156
247
 
157
- What is verifiable: the backend host serves no `robots.txt` at all, requires no
158
- API key, and did not throttle ten consecutive requests during testing. Those
159
- are the conditions this server operates under, and it does not take them as
160
- permission to be greedy. It sends one request per second at most, identifies
161
- itself, caches what it reads, and carries a link back to Metacritic on every
162
- result, including on every quoted review.
248
+ ## Privacy
163
249
 
164
- If Metacritic would rather it did not exist, opening an issue is enough.
250
+ This server collects nothing about you and sends nothing to its author. It runs
251
+ on your machine, contacts `backend.metacritic.com` and nothing else, holds its
252
+ answers in memory while it runs, and writes nothing to disk.
253
+ [PRIVACY.md](PRIVACY.md) states what a request carries and which settings change
254
+ any of it.
165
255
 
166
256
  ## Development
167
257
 
168
258
  ```bash
169
259
  npm install
170
- npm run build:fixtures # regenerate the JSON test fixtures
171
- npm test # unit tests, no network
172
- npm run typecheck
173
- npm run build
174
- MC_LIVE=1 npm run test:live # hits the real site, excluded from CI
175
- npm run inspector # explore the tools in the MCP Inspector
260
+ npm run build:fixtures
261
+ npm test
262
+ npm run check
176
263
  ```
177
264
 
178
- Fixtures are generated, not captured: every title, quote, publication and
179
- studio in `test/fixtures` is invented, so the tests are deterministic and no
180
- third-party content lives in this repository.
181
-
182
- The API layer (`src/mc`) does not import the MCP SDK and is published
183
- separately as `mcp-metacritic/client`, so it can be used as a plain library. It
184
- enforces the pacing floor and the identifying User-Agent itself, so those hold
185
- for a library consumer too.
186
-
187
- ## Reviews, scores and copyright
188
-
189
- Metascores, review texts and the editorial work behind them belong to
190
- Metacritic and to the publications it aggregates. This project claims no rights
191
- over them and ships none of their content.
192
-
193
- Review quotes are returned as excerpts, capped in length, always with the
194
- publication named and with a link to the original article where the site
195
- provides one. If you repeat a quote, keep both. If you cite a score, credit
196
- Metacritic and link the entry: every result carries a `source_url`.
197
-
198
- This is an unofficial project, with no affiliation to or endorsement by
199
- Metacritic or Fandom.
265
+ Tests run against generated fixtures and make no network request. The live suite,
266
+ `npm run test:live`, makes one request per route and runs nightly against the
267
+ site itself.
200
268
 
201
269
  ## Contributing
202
270
 
203
- Bugs, questions and ideas all belong in
204
- [the issue tracker](https://github.com/smeet666/mcp-metacritic/issues). Pull requests
205
- are welcome; please open an issue first so we can agree on what the right
206
- answer is before you write it. [CONTRIBUTING.md](CONTRIBUTING.md) has the
207
- detail, and [SECURITY.md](SECURITY.md) covers anything exploitable.
208
-
209
- ## Support
210
-
211
- These servers are free and stay free. If one of them saved you an afternoon,
212
- you can [buy me a coffee](https://buymeacoffee.com/smeet666).
271
+ Bugs, questions and ideas belong in
272
+ [the issue tracker](https://github.com/smeet666/mcp-metacritic/issues). Pull
273
+ requests are welcome; opening an issue first helps agree on the shape of the
274
+ change. See [CONTRIBUTING.md](CONTRIBUTING.md).
213
275
 
214
276
  ## License
215
277
 
216
- MIT. See [LICENSE](./LICENSE). The license covers this source code only, not the
217
- data retrieved through it.
278
+ MIT, see [LICENSE](LICENSE). The scores and the reviews belong to Metacritic and
279
+ to the publications it quotes.
218
280
 
219
281
  ---
220
282
 
@@ -222,13 +284,22 @@ data retrieved through it.
222
284
 
223
285
  # mcp-metacritic (français)
224
286
 
225
- Un serveur [MCP](https://modelcontextprotocol.io) pour
226
- [Metacritic](https://www.metacritic.com). Cherchez des films, séries et jeux,
227
- lisez les Metascores et les notes du public, et citez ce qu'ont écrit les
228
- critiques, avec la publication nommée et l'article lié. **Sans clé d'API, sans
229
- compte, sans configuration.**
287
+ _[English version](#mcp-metacritic)_
288
+
289
+ [Metacritic](https://www.metacritic.com) rassemble ce que la critique et le
290
+ public ont dit des films, des séries et des jeux vidéo. Chaque fiche porte
291
+ l'année, la classification par âge, les genres, et deux notes qui lui sont
292
+ propres : le Metascore, moyenne pondérée des critiques professionnelles, et la
293
+ note des utilisateurs, sur dix, donnée par les inscrits. Sous chaque fiche se
294
+ trouvent les critiques elles-mêmes, avec la publication qui les a signées et la
295
+ phrase citée.
230
296
 
231
- ## Démarrage rapide
297
+ Ce serveur relie un client de conversation à ce catalogue. On peut y chercher un
298
+ titre, lire sa fiche avec ses notes et ses détails, parcourir un catalogue par
299
+ note, par fraîcheur ou par popularité, et lire les critiques d'un titre, filtrées
300
+ par source et par tonalité. Aucune clé d'API, aucun compte.
301
+
302
+ ## Installation
232
303
 
233
304
  **Installation en un clic**
234
305
 
@@ -241,7 +312,7 @@ compte, sans configuration.**
241
312
  claude mcp add metacritic -- npx -y mcp-metacritic
242
313
  ```
243
314
 
244
- **Claude Desktop, Cursor, et tout client utilisant le format standard**
315
+ **Claude Desktop, Cursor, et tout client au format de configuration standard**
245
316
 
246
317
  ```json
247
318
  {
@@ -254,6 +325,9 @@ claude mcp add metacritic -- npx -y mcp-metacritic
254
325
  }
255
326
  ```
256
327
 
328
+ Node 24 ou plus récent est nécessaire, et aucune variable d'environnement n'est à
329
+ renseigner.
330
+
257
331
  ### Avec Docker
258
332
 
259
333
  ```json
@@ -261,155 +335,220 @@ claude mcp add metacritic -- npx -y mcp-metacritic
261
335
  "mcpServers": {
262
336
  "metacritic": {
263
337
  "command": "docker",
264
- "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-metacritic:2.0.0"]
338
+ "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-metacritic:2.1.0"]
265
339
  }
266
340
  }
267
341
  }
268
342
  ```
269
343
 
270
- `-i` garde l'entrée standard ouverte, qui est le canal du protocole, et aucun
271
- `-t` n'est passé : un terminal réécrit le flux et le casse. Le conteneur a besoin
272
- d'un accès HTTPS sortant vers `www.metacritic.com` et `backend.metacritic.com`, et de rien d'autre :
273
- aucun volume, aucun port, aucune variable d'environnement, aucun identifiant.
274
-
275
- **Bundle, sans npm**
276
-
277
- Téléchargez `mcp-metacritic-<version>.mcpb` depuis
278
- [la dernière release](https://github.com/smeet666/mcp-metacritic/releases/latest) et
279
- ouvrez-le. Un client compatible avec les bundles MCP l'installe seul, sans npm
280
- ni fichier de configuration à modifier. Le bundle embarque ses dépendances,
281
- donc rien n'est téléchargé à l'installation.
282
-
283
- ## Outils
284
-
285
- | Outil | Rôle | Paramètres principaux |
286
- | --------------- | --------------------------------------------------------- | ------------------------------------------------- |
287
- | `search_titles` | Trouve films, séries et jeux par titre. Lignes compactes. | `query`, `kind`, `limit` |
288
- | `get_title` | Lit une fiche, section par section. | `slug`, `kind`, `sections`, `max_chars`, `offset` |
289
- | `get_reviews` | Critiques presse ou public, filtrables par verdict. | `slug`, `kind`, `source`, `sentiment`, `limit` |
290
- | `browse_titles` | Classements : mieux notés, plus récents, plus consultés. | `kind`, `sort`, `genre`, `limit`, `offset` |
291
-
292
- La recherche renvoie un `slug` et un `kind` pour chaque résultat, que les autres
293
- outils reprennent. C'est l'enchaînement prévu : chercher, puis lire.
294
-
295
- Le serveur est en **lecture seule**. Il n'écrit rien vers Metacritic.
296
-
297
- ### Ce qu'il faut savoir
298
-
299
- **Les deux notes ne sont pas sur la même échelle.** Le Metascore critique va
300
- jusqu'à 100 et la note du public jusqu'à 10, donc 73 et 8,9 décrivent des avis
301
- voisins. Chaque note renvoyée porte son propre `max`, et les descriptions
302
- d'outils le disent, parce que les additionner est l'erreur la plus facile à
303
- commettre avec ces données.
304
-
305
- **Les sections sont à la demande, car une fiche est volumineuse.** Une réponse
306
- de détail fait 46 Ko pour un film, essentiellement des variantes d'illustration.
307
- `get_title` renvoie `basic` et `scores` par défaut ; `awards`, `production`,
308
- `networks` et `where_to_watch` coûtent une requête chacune et ne sont récupérées
309
- que si on les demande.
310
-
311
- **Les critiques sont un échantillon, pas une page.** Metacritic en compte 36 sur
312
- un film donné et en sert 7 par cette route, quelle que soit la pagination
313
- demandée. L'outil le dit au lieu de présenter l'échantillon comme la liste
314
- complète, et renvoie vers la fiche où le reste se lit.
315
-
316
- **Un échec n'est jamais un résultat vide.** Chaque réponse porte soit un objet
317
- `data`, soit un tableau `errors`, et ce serveur lit cette distinction au lieu de
318
- la deviner. Une fiche absente produit une erreur avec un code ; une requête qui
319
- n'a pas abouti dit qu'elle n'a pas abouti, jamais que Metacritic ne publie rien.
320
- Ce sont deux réponses très différentes, qu'un modèle ne peut pas distinguer seul.
321
-
322
- **La recherche porte sur les titres, et rien d'autre.** Elle ne retrouve pas une
323
- fiche depuis un élément d'intrigue, une personne ou un studio, elle ne pagine
324
- pas, et sur une requête de plusieurs mots le site compte large : « the matrix »
325
- annonce plus de 55 000 entrées parce qu'il compte tout ce qui correspond à l'un
326
- des deux mots.
327
-
328
- **La pagination des classements est approximative.** Metacritic n'ordonne pas
329
- les ex æquo de façon stable, donc une entrée peut apparaître sur deux pages
330
- consécutives. Dédoublonnez par slug plutôt que de compter les lignes.
344
+ `-i` garde l'entrée standard ouverte, qui est le canal du protocole, et `-t` est
345
+ omis parce qu'un TTY réécrit le flux. Le conteneur a besoin d'un accès HTTPS
346
+ sortant vers `backend.metacritic.com`, et de rien d'autre : aucun volume, aucun
347
+ port, aucun identifiant.
348
+
349
+ ### Bundle, sans npm
350
+
351
+ Téléchargez `mcp-metacritic-2.1.0.mcpb` depuis
352
+ [la dernière publication](https://github.com/smeet666/mcp-metacritic/releases/latest)
353
+ et ouvrez-le. Un client qui gère les bundles MCP l'installe seul, sans npm et
354
+ sans fichier de configuration à modifier. Le bundle emporte ses dépendances, donc
355
+ rien n'est téléchargé à l'installation.
356
+
357
+ ## Ce qu'on peut demander
358
+
359
+ - « Qu'a pensé la critique de Matrix ? »
360
+ - « Lis-moi quelques critiques négatives de ce jeu. »
361
+ - « Quels sont les films d'horreur les mieux notés ? »
362
+ - « Comment la note du public se compare-t-elle au Metascore ? »
363
+ - « Qu'est-il sorti récemment qui a été bien reçu ? »
364
+
365
+ Le chemin ordinaire va d'une recherche à une fiche : une ligne porte un `slug` et
366
+ un `kind`, et `get_title` comme `get_reviews` reprennent les deux ensemble.
367
+
368
+ ## Les outils
369
+
370
+ | Outil | Ce qu'il fait |
371
+ | --------------- | ------------------------------------------------------------- |
372
+ | `search_titles` | Trouve des films, des séries et des jeux par leur titre. |
373
+ | `get_title` | Lit une fiche, ses notes et ses détails. |
374
+ | `get_reviews` | Lit les critiques d'une fiche, par source et par tonalité. |
375
+ | `browse_titles` | Liste un catalogue par note, par fraîcheur ou par popularité. |
376
+
377
+ Un titre s'adresse par son `slug` accompagné de son `kind`, un même slug pouvant
378
+ nommer un film et un jeu.
379
+
380
+ ### `search_titles`
381
+
382
+ Trouve des films, des séries et des jeux par leur titre.
383
+
384
+ | Argument | Type | Requis | Ce qu'il fait |
385
+ | -------- | ---------------------------------------------- | ------ | ------------------------- |
386
+ | `query` | chaîne, au moins 1 caractère | oui | Un titre, ou une partie. |
387
+ | `kind` | `movie`, `show`, `game` ou `any`, défaut `any` | non | Le catalogue où chercher. |
388
+ | `limit` | entier, 1 à 50, défaut `10` | non | Lignes à servir. |
389
+
390
+ **En retour :** des lignes portant `slug` et `kind`, que `get_title` et
391
+ `get_reviews` reprennent ensemble ; `title` ; `year` ; `release_date` ; `rating`,
392
+ la classification par âge telle que publiée ; `metascore` ; `user_score`, que la
393
+ route de recherche ne publie pour aucune ligne et qui y vaut `null` partout ; et
394
+ `source_url`. **Une note que le site n'a pas calculée vaut `null`, jamais `0` :**
395
+ sur une échelle qui commence à zéro les deux seraient indiscernables, et un titre
396
+ avec trop peu de critiques n'en porte aucune.
397
+
398
+ ### `get_title`
399
+
400
+ Lit une fiche. Les parties lourdes se demandent au lieu d'être servies par
401
+ défaut, et chacune au-delà du défaut coûte une requête.
402
+
403
+ | Argument | Type | Requis | Ce qu'il fait |
404
+ | ----------- | ---------------------------------------------------------------------------------------------------------------- | ------ | ------------------------------------ |
405
+ | `slug` | chaîne, au moins 1 caractère | oui | L'identifiant d'une ligne. |
406
+ | `kind` | `movie`, `show` ou `game` | oui | Le catalogue dont il relève. |
407
+ | `sections` | tableau de `basic`, `scores`, `awards`, `production`, `networks`, `where_to_watch`, défaut `["basic", "scores"]` | non | Les parties à rendre. |
408
+ | `max_chars` | entier, 200 à 20000, défaut `4000` | non | La longueur de description à servir. |
409
+ | `offset` | entier, 0 ou plus, défaut `0` | non | Où reprendre la description. |
410
+
411
+ **En retour :** la fiche que porte une ligne de recherche, plus `description`,
412
+ `tagline`, `genres`, `duration_minutes` et `imdb_id`, chacun `null` là où la page
413
+ n'indique rien. `total_chars`, `returned_chars` et `offset` décrivent la tranche
414
+ de description servie.
415
+
416
+ ### `get_reviews`
417
+
418
+ Lit les critiques d'une fiche.
419
+
420
+ | Argument | Type | Requis | Ce qu'il fait |
421
+ | ----------- | -------------------------------------------------------- | ------ | ---------------------------------- |
422
+ | `slug` | chaîne, au moins 1 caractère | oui | L'identifiant d'une ligne. |
423
+ | `kind` | `movie`, `show` ou `game` | oui | Le catalogue dont il relève. |
424
+ | `source` | `critic` ou `user`, défaut `critic` | non | De qui lire les critiques. |
425
+ | `sentiment` | `all`, `positive`, `neutral` ou `negative`, défaut `all` | non | La tonalité exigée d'une critique. |
426
+ | `limit` | entier, 1 à 50, défaut `10` | non | Critiques à servir. |
427
+ | `offset` | entier, 0 ou plus, défaut `0` | non | Critiques à sauter, pour paginer. |
428
+
429
+ **En retour :** `reviews`, chacune avec sa `quote` telle que publiée, son
430
+ `score`, le `max` sur lequel cette note est donnée, qui vaut 100 pour un critique
431
+ et 10 pour un utilisateur, et la `publication` qui l'a signée. **Nommez la
432
+ publication quand vous citez une critique.** `total_available` compte toutes les
433
+ critiques que Metacritic tient de cette source pour cette fiche, quelle que soit
434
+ la tonalité demandée, puisque la tonalité trie l'échantillon reçu et non la
435
+ requête qui l'a cherché. `next_offset` poursuit.
436
+
437
+ ### `browse_titles`
438
+
439
+ Liste un catalogue.
440
+
441
+ | Argument | Type | Requis | Ce qu'il fait |
442
+ | -------- | ---------------------------------------------- | ------ | ------------------------------------- |
443
+ | `kind` | `movie`, `show` ou `game`, défaut `movie` | non | Le catalogue à lister. |
444
+ | `sort` | `score`, `recent` ou `popular`, défaut `score` | non | L'ordre des lignes. |
445
+ | `genre` | chaîne | non | Un seul nom de genre, comme `Horror`. |
446
+ | `limit` | entier, 1 à 50, défaut `20` | non | Lignes à servir. |
447
+ | `offset` | entier, 0 ou plus, défaut `0` | non | Lignes à sauter, pour paginer. |
448
+
449
+ **En retour :** les lignes que rend `search_titles`, avec `total_available`,
450
+ `offset`, `next_offset` et les `kind`, `sort` et `genre` sous lesquels la liste a
451
+ été lue.
452
+
453
+ ## Deux notes, deux choses mesurées
454
+
455
+ Le Metascore est une moyenne pondérée des critiques professionnelles, sur 100. La
456
+ note des utilisateurs est la moyenne de ce qu'ont donné les membres inscrits, sur 10. Elles mesurent des populations différentes sur des échelles différentes, et
457
+ un titre peut porter l'une sans l'autre. Lisez chacune avec le `max` que ses
458
+ critiques indiquent, et rapportez une note absente comme absente.
331
459
 
332
460
  ## Configuration
333
461
 
334
- Toutes les variables sont optionnelles, à déclarer dans le bloc `env` de votre client.
335
-
336
- | Variable | Défaut | Rôle |
337
- | ------------------------ | ------------------------------------- | ----------------------------------------------------------------- |
338
- | `MC_USER_AGENT` | `mcp-metacritic v<version> (<dépôt>)` | User-Agent envoyé à Metacritic. |
339
- | `MC_MIN_INTERVAL_MS` | `1000` | Écart minimal entre requêtes. Sous 500 ms, la valeur est ignorée. |
340
- | `MC_TIMEOUT_MS` | `15000` | Délai d'attente par requête. |
341
- | `MC_MAX_RETRIES` | `3` | Tentatives en cas de refus ou d'erreur passagère. |
342
- | `MC_CACHE_TTL_MS` | `86400000` | Durée de vie du cache catalogue (24 heures). |
343
- | `MC_SCORES_CACHE_TTL_MS` | `3600000` | Durée de vie du cache notes et critiques (1 heure). |
344
- | `MC_CACHE_MAX_ENTRIES` | `200` | Taille des caches mémoire. |
345
- | `MC_LOG_LEVEL` | `error` | `silent`, `error`, `info` ou `debug`. Logs sur stderr. |
346
-
347
- Un User-Agent se faisant passer pour un navigateur se voit adjoindre l'identité
348
- du projet, pour que le trafic reste attribuable quoi que règle l'appelant.
462
+ Chaque variable est facultative. Elles se posent dans le bloc `env` de la
463
+ configuration du client.
464
+
465
+ | Variable | Défaut | Ce qu'elle fait |
466
+ | ------------------------ | -------------------- | ---------------------------------------------------------------------------------- |
467
+ | `MC_USER_AGENT` | l'identité du projet | Nomme votre application auprès du site, avec une adresse où joindre une personne. |
468
+ | `MC_MIN_INTERVAL_MS` | `1000` | Écart entre deux requêtes, de 500 à 60000. |
469
+ | `MC_TIMEOUT_MS` | `15000` | Délai d'une requête, de 1000 à 120000. |
470
+ | `MC_MAX_RETRIES` | `3` | Tentatives après un échec passager, de 0 à 10. |
471
+ | `MC_CACHE_TTL_MS` | `86400000` | Durée pendant laquelle une fiche reste en mémoire, de 0 à 604800000. |
472
+ | `MC_SCORES_CACHE_TTL_MS` | `3600000` | Durée pendant laquelle les notes et critiques restent en mémoire, de 0 à 86400000. |
473
+ | `MC_CACHE_MAX_ENTRIES` | `200` | Réponses gardées en mémoire à la fois, de 0 à 10000. |
474
+ | `MC_LOG_LEVEL` | `error` | `silent`, `error`, `info` ou `debug`, écrit sur la sortie d'erreur. |
475
+
476
+ Les notes bougent au fil des critiques, surtout autour d'une sortie, donc elles
477
+ sont gardées une heure là où une fiche l'est un jour. Une valeur hors de sa plage
478
+ retombe sur le défaut, et la raison est écrite sur la sortie d'erreur.
479
+
480
+ ## Erreurs
481
+
482
+ Chaque échec porte un des six codes, un message, et quand cela aide une
483
+ indication du geste suivant.
484
+
485
+ | Code | Ce qui s'est passé | Que faire |
486
+ | --------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
487
+ | `not_found` | Le site a répondu, et n'a pas cette fiche. | Vérifiez le slug et le type avec `search_titles`. |
488
+ | `invalid_input` | Les arguments ont été refusés avant toute requête. | Lisez le message, qui nomme l'argument. |
489
+ | `rate_limited` | Le site demande à ce client de ralentir. | Attendez les secondes indiquées et rappelez avec les mêmes arguments. La fiche est toujours là. |
490
+ | `parse_failure` | La réponse est arrivée dans une forme illisible ici. | Signalez-le sur [le suivi d'incidents](https://github.com/smeet666/mcp-metacritic/issues). |
491
+ | `network_error` | La requête n'a pas abouti. | Réessayez sous peu. |
492
+ | `timeout` | La requête a dépassé son délai. | Augmentez `MC_TIMEOUT_MS`, ou demandez moins de lignes. |
493
+
494
+ ## Comme bibliothèque
495
+
496
+ La couche qui lit le site est publiée seule, avec son rythme, son cache et ses
497
+ erreurs, sans protocole attaché.
498
+
499
+ ```ts
500
+ import { McClient } from "mcp-metacritic/client";
501
+
502
+ const client = new McClient();
503
+ const { data, cached } = await client.getDetail("movie", "the-matrix");
504
+ console.log(data.title, data.metascore, cached);
505
+ ```
349
506
 
350
- ## Fonctionnement
507
+ Chaque lecture répond `{ data, cached }`, et lève une erreur portant un des six
508
+ codes. Le plancher entre deux requêtes tient également ici.
351
509
 
352
- Le site de Metacritic est servi par un backend JSON à `backend.metacritic.com`,
353
- et ce serveur appelle les mêmes routes que leurs propres pages. Il envoie une
354
- requête à la fois, s'impose un rythme, ralentit en cas de refus, et garde deux
355
- caches mémoire : un jour pour les fiches, qui ne bougent qu'à l'édition, et une
356
- heure pour les notes et critiques, qui évoluent au fil des publications.
510
+ ## Rythme et attribution
357
511
 
358
- Les liens de streaming arrivent enveloppés dans des redirections publicitaires
359
- d'environ 800 caractères, avec la vraie destination à l'intérieur. Ce serveur
360
- les déballe : vous obtenez l'URL du fournisseur, pas celle d'un traqueur.
512
+ Les requêtes partent une à une avec au moins une seconde entre elles, et le
513
+ plancher d'une demi-seconde tient quelle que soit la configuration. Le
514
+ `User-Agent` se termine toujours par l'identité du projet et une adresse où
515
+ joindre une personne.
361
516
 
362
- ### À propos des conditions d'utilisation
517
+ Chaque résultat porte l'adresse de la page Metacritic, et chaque critique citée
518
+ porte la publication qui l'a signée. Les critiques appartiennent à leurs auteurs
519
+ et aux publications qui les ont publiées.
363
520
 
364
- `metacritic.com/terms-of-use/` renvoie une 404, les conditions n'ont donc pas pu
365
- être lues. Autant le dire clairement plutôt que de le passer sous silence.
521
+ Ce MCP est un projet non officiel, sans affiliation à Metacritic.
366
522
 
367
- Ce qui est vérifiable : l'hôte du backend ne sert aucun `robots.txt`, ne demande
368
- aucune clé, et n'a ralenti aucune de dix requêtes consécutives lors des tests.
369
- Ce sont les conditions dans lesquelles ce serveur opère, et il n'y voit pas une
370
- autorisation d'être gourmand. Il envoie au plus une requête par seconde,
371
- s'identifie, met en cache ce qu'il lit, et porte un lien vers Metacritic sur
372
- chaque résultat, y compris sur chaque critique citée.
523
+ ## Confidentialité
373
524
 
374
- Si Metacritic préfère qu'il n'existe pas, ouvrir une issue suffit.
525
+ Ce serveur ne collecte rien sur vous et n'envoie rien à son auteur. Il tourne sur
526
+ votre machine, ne joint que `backend.metacritic.com`, garde ses réponses en
527
+ mémoire le temps qu'il tourne, et n'écrit rien sur le disque.
528
+ [PRIVACY.md](PRIVACY.md) dit ce qu'une requête emporte et quels réglages changent
529
+ cela.
375
530
 
376
531
  ## Développement
377
532
 
378
533
  ```bash
379
534
  npm install
380
- npm run build:fixtures # régénère les fixtures JSON de test
381
- npm test # tests unitaires, sans réseau
382
- npm run typecheck
383
- npm run build
384
- MC_LIVE=1 npm run test:live # touche le vrai site, exclu de la CI
385
- npm run inspector # explorer les outils dans le MCP Inspector
535
+ npm run build:fixtures
536
+ npm test
537
+ npm run check
386
538
  ```
387
539
 
388
- Les fixtures sont générées, pas capturées : chaque titre, citation, publication
389
- et studio de `test/fixtures` est inventé, ce qui rend les tests déterministes et
390
- évite de stocker du contenu tiers dans ce dépôt.
391
-
392
- La couche API (`src/mc`) n'importe pas le SDK MCP et est publiée séparément sous
393
- `mcp-metacritic/client`, utilisable comme simple bibliothèque. Elle applique
394
- elle-même le plancher de cadence et le User-Agent identifiant, qui valent donc
395
- aussi pour qui l'utilise en bibliothèque.
396
-
397
- ## Critiques, notes et droits d'auteur
398
-
399
- Les Metascores, les textes de critiques et le travail éditorial qui les
400
- sous-tend appartiennent à Metacritic et aux publications qu'il agrège. Ce projet
401
- ne revendique aucun droit dessus et n'embarque aucun de leurs contenus.
540
+ Les tests s'exécutent sur des fixtures engendrées et n'émettent aucune requête.
541
+ La suite en direct, `npm run test:live`, émet une requête par route et tourne
542
+ chaque nuit contre le site lui-même.
402
543
 
403
- Les citations sont renvoyées sous forme d'extraits, de longueur bornée, toujours
404
- avec la publication nommée et un lien vers l'article d'origine quand le site en
405
- fournit un. Si vous reprenez une citation, conservez les deux. Si vous citez une
406
- note, créditez Metacritic et liez la fiche : chaque résultat porte un
407
- `source_url`.
544
+ ## Contribuer
408
545
 
409
- Projet non officiel, sans affiliation à Metacritic ou Fandom ni approbation de
410
- leur part.
546
+ Les anomalies, les questions et les idées ont leur place dans
547
+ [le suivi d'incidents](https://github.com/smeet666/mcp-metacritic/issues). Les
548
+ propositions de modification sont bienvenues ; ouvrir un ticket d'abord aide à
549
+ s'accorder sur la forme du changement. Voir [CONTRIBUTING.md](CONTRIBUTING.md).
411
550
 
412
551
  ## Licence
413
552
 
414
- MIT, voir [LICENSE](./LICENSE). La licence couvre uniquement le code source, pas
415
- les données récupérées par son intermédiaire.
553
+ MIT, voir [LICENSE](LICENSE). Les notes et les critiques appartiennent à
554
+ Metacritic et aux publications qu'il cite.