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 +13 -0
- package/dist/index.js +40 -23
- package/dist/index.js.map +1 -1
- package/dist/mc/client.js +1 -1
- package/dist/mc/client.js.map +1 -1
- package/package.json +5 -1
- package/server.json +18 -3
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.
|
|
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,
|
|
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
|
-
|
|
1304
|
-
|
|
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 ${
|
|
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
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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)
|