mcp-metacritic 1.0.1 → 1.0.3

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
@@ -167,6 +167,19 @@ Metacritic and link the entry: every result carries a `source_url`.
167
167
  This is an unofficial project, with no affiliation to or endorsement by
168
168
  Metacritic or Fandom.
169
169
 
170
+ ## Contributing
171
+
172
+ Bugs, questions and ideas all belong in
173
+ [the issue tracker](https://github.com/smeet666/mcp-metacritic/issues). Pull requests
174
+ are welcome; please open an issue first so we can agree on what the right
175
+ answer is before you write it. [CONTRIBUTING.md](CONTRIBUTING.md) has the
176
+ detail, and [SECURITY.md](SECURITY.md) covers anything exploitable.
177
+
178
+ ## Support
179
+
180
+ These servers are free and stay free. If one of them saved you an afternoon,
181
+ you can [buy me a coffee](https://buymeacoffee.com/smeet666).
182
+
170
183
  ## License
171
184
 
172
185
  MIT. See [LICENSE](./LICENSE). The license covers this source code only, not the
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
 
6
6
  // src/version.ts
7
- var PKG_VERSION = "1.0.1";
7
+ var PKG_VERSION = "1.0.3";
8
8
  var REPO_URL = "https://github.com/smeet666/mcp-metacritic";
9
9
 
10
10
  // src/config.ts
@@ -129,6 +129,7 @@ function createLogger(level) {
129
129
 
130
130
  // src/server.ts
131
131
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
132
+ import { z as z6 } from "zod";
132
133
 
133
134
  // src/mc/cache.ts
134
135
  var TtlLruCache = class {
@@ -946,7 +947,13 @@ function toScoreOut(score) {
946
947
  sentiment: score.sentiment
947
948
  };
948
949
  }
949
- function ok(structured, body, trailer = ATTRIBUTION) {
950
+ function ok(structured, body, options = {}) {
951
+ const attribution = options.sourceUrl ? `${ATTRIBUTION} \u2014 ${options.sourceUrl}` : ATTRIBUTION;
952
+ const noteLines = (options.notes ?? []).map((note) => `Note: ${note}`);
953
+ while (noteLines.length > 0 && noteLines.join("\n").length > MAX_TEXT_MIRROR_CHARS / 2) {
954
+ noteLines.pop();
955
+ }
956
+ const trailer = [...noteLines, attribution].join("\n");
950
957
  const cutMarker = "\n\n[shortened; the full result is in the structured output]";
951
958
  const reserved = `
952
959
 
@@ -1074,7 +1081,8 @@ ${renderTitleList(results)}`;
1074
1081
  next_offset: nextOffset,
1075
1082
  notes
1076
1083
  },
1077
- summary
1084
+ summary,
1085
+ { notes }
1078
1086
  );
1079
1087
  } catch (error) {
1080
1088
  return toToolError(error);
@@ -1194,7 +1202,8 @@ ${listing}`;
1194
1202
  next_offset: nextOffset,
1195
1203
  notes
1196
1204
  },
1197
- summary
1205
+ summary,
1206
+ { notes, sourceUrl }
1198
1207
  );
1199
1208
  } catch (error) {
1200
1209
  return toToolError(error);
@@ -1299,23 +1308,23 @@ async function runGetTitle(client, args) {
1299
1308
  notes
1300
1309
  };
1301
1310
  if (wanted.has("awards")) structured.awards = item.awards;
1311
+ const PRODUCTION_CAP = 25;
1312
+ const production = wanted.has("production") ? item.production.slice(0, PRODUCTION_CAP) : [];
1302
1313
  if (wanted.has("production")) {
1303
- const CAP = 25;
1304
- structured.production = item.production.slice(0, CAP);
1305
- if (item.production.length > CAP) {
1314
+ structured.production = production;
1315
+ if (item.production.length > PRODUCTION_CAP) {
1306
1316
  notes.push(
1307
- `${item.production.length} companies are credited and the first ${CAP} are shown. The list mixes producers with distributors and home-video labels across every territory.`
1317
+ `${item.production.length} companies are credited and the first ${PRODUCTION_CAP} are shown. The list mixes producers with distributors and home-video labels across every territory.`
1308
1318
  );
1309
1319
  }
1310
1320
  }
1311
1321
  if (wanted.has("networks")) structured.networks = item.networks;
1312
1322
  const offers = wanted.has("where_to_watch") ? await watchOffers(client, item, args.kind, notes) : [];
1313
1323
  if (wanted.has("where_to_watch")) structured.where_to_watch = offers;
1314
- return ok(
1315
- structured,
1316
- render(item, slice, criticScore, userScore, wanted, offers),
1317
- `${ATTRIBUTION} \u2014 ${item.sourceUrl}`
1318
- );
1324
+ return ok(structured, render(item, slice, criticScore, userScore, wanted, offers, production), {
1325
+ notes,
1326
+ sourceUrl: item.sourceUrl
1327
+ });
1319
1328
  } catch (error) {
1320
1329
  return toToolError(error);
1321
1330
  }
@@ -1353,7 +1362,7 @@ async function watchOffers(client, item, kind, notes) {
1353
1362
  return [];
1354
1363
  }
1355
1364
  }
1356
- function render(item, description, critic, user, wanted, offers) {
1365
+ function render(item, description, critic, user, wanted, offers, production) {
1357
1366
  const yearShown = item.year !== null && !item.title.includes(`(${item.year})`);
1358
1367
  const header = [item.title, yearShown ? `(${item.year})` : "", `\xB7 ${item.kind}`].filter(Boolean).join(" ");
1359
1368
  const lines = [header];
@@ -1385,6 +1394,12 @@ function render(item, description, critic, user, wanted, offers) {
1385
1394
  if (wanted.has("networks") && item.networks.length > 0) {
1386
1395
  lines.push("", `Networks: ${item.networks.join(", ")}`);
1387
1396
  }
1397
+ if (wanted.has("production")) {
1398
+ lines.push(
1399
+ "",
1400
+ production.length === 0 ? "Production: no company credited." : `Production: ${production.map((company) => company.name).join(", ")}`
1401
+ );
1402
+ }
1388
1403
  if (wanted.has("where_to_watch")) {
1389
1404
  lines.push("", offers.length === 0 ? "Where to watch: nothing listed." : "Where to watch:");
1390
1405
  for (const o of offers) lines.push(` ${o.provider} (${o.kind})${o.url ? ` \u2014 ${o.url}` : ""}`);
@@ -1445,7 +1460,9 @@ async function runSearchTitles(client, args) {
1445
1460
  }
1446
1461
  const summary = results.length === 0 ? `No Metacritic entry matched "${args.query}".` : `${results.length} entr${results.length === 1 ? "y" : "ies"} for "${args.query}":
1447
1462
  ${renderTitleList(results)}`;
1448
- return ok({ query: args.query, results, total_available: data.totalResults, notes }, summary);
1463
+ return ok({ query: args.query, results, total_available: data.totalResults, notes }, summary, {
1464
+ notes
1465
+ });
1449
1466
  } catch (error) {
1450
1467
  return toToolError(error);
1451
1468
  }
@@ -1477,8 +1494,8 @@ function createServer(options = {}) {
1477
1494
  {
1478
1495
  title: "Search Metacritic",
1479
1496
  description: searchTitlesDescription,
1480
- inputSchema: searchTitlesInputShape,
1481
- outputSchema: searchTitlesOutputShape,
1497
+ inputSchema: z6.object(searchTitlesInputShape),
1498
+ outputSchema: z6.object(searchTitlesOutputShape),
1482
1499
  annotations: READ_ONLY
1483
1500
  },
1484
1501
  async (args) => runSearchTitles(client, args)
@@ -1488,8 +1505,8 @@ function createServer(options = {}) {
1488
1505
  {
1489
1506
  title: "Read an entry",
1490
1507
  description: getTitleDescription,
1491
- inputSchema: getTitleInputShape,
1492
- outputSchema: getTitleOutputShape,
1508
+ inputSchema: z6.object(getTitleInputShape),
1509
+ outputSchema: z6.object(getTitleOutputShape),
1493
1510
  annotations: READ_ONLY
1494
1511
  },
1495
1512
  async (args) => runGetTitle(client, args)
@@ -1499,8 +1516,8 @@ function createServer(options = {}) {
1499
1516
  {
1500
1517
  title: "Read reviews",
1501
1518
  description: getReviewsDescription,
1502
- inputSchema: getReviewsInputShape,
1503
- outputSchema: getReviewsOutputShape,
1519
+ inputSchema: z6.object(getReviewsInputShape),
1520
+ outputSchema: z6.object(getReviewsOutputShape),
1504
1521
  annotations: READ_ONLY
1505
1522
  },
1506
1523
  async (args) => runGetReviews(client, args)
@@ -1510,8 +1527,8 @@ function createServer(options = {}) {
1510
1527
  {
1511
1528
  title: "Browse rankings",
1512
1529
  description: browseTitlesDescription,
1513
- inputSchema: browseTitlesInputShape,
1514
- outputSchema: browseTitlesOutputShape,
1530
+ inputSchema: z6.object(browseTitlesInputShape),
1531
+ outputSchema: z6.object(browseTitlesOutputShape),
1515
1532
  annotations: READ_ONLY
1516
1533
  },
1517
1534
  async (args) => runBrowseTitles(client, args)