cito-mcp 0.4.2 → 0.4.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
@@ -213,9 +213,27 @@ When a key is `null` the image does not exist for that entity; when the whole
213
213
  never partially shaped — if any image exists, all four keys are present.
214
214
 
215
215
  Tennis is the exception on purpose: the feed supplies a single `portrait_url`
216
- (Wikimedia) and no square crop, so that one URL fills both `headshotUrl` and
217
- `imageUrl`, and `proxiedImageUrl` stays `null` because only `ufc.com` URLs have
218
- a proxy. A card with the real photo slightly cropped beats a faceless card.
216
+ (Wikimedia Commons) and no square crop, so that one URL fills both `headshotUrl`
217
+ and `imageUrl`, and `proxiedImageUrl` stays `null` because only `ufc.com` URLs have
218
+ a proxy.
219
+
220
+ **Tennis images carry their licence and credit** — and a UI is expected to render
221
+ them:
222
+
223
+ ```jsonc
224
+ "images": {
225
+ "headshotUrl": "https://commons.wikimedia.org/wiki/Special:FilePath/Hoang_WMQ18_%2833%29_%2842647295495%29.jpg?width=400",
226
+ "license": "CC BY-SA 2.0",
227
+ "attribution": "si.robi",
228
+ "sourceUrl": "https://commons.wikimedia.org/wiki/File:Hoang_WMQ18_(33)_(42647295495).jpg"
229
+ }
230
+ ```
231
+
232
+ Commons portraits are mostly CC BY / CC BY-SA, which for commercial display
233
+ generally requires naming the author and stating the licence. `attribution` is
234
+ `null` for public-domain and CC0 images, which need no credit — that is a fact,
235
+ not missing data. Anything without a defensible licence has its URL removed
236
+ upstream, so a non-null `headshotUrl` always has a `license` beside it.
219
237
 
220
238
  ### Games
221
239
 
@@ -54,16 +54,29 @@ function identityFrom(game, raw, idHint, slugHint) {
54
54
  */
55
55
  images: {
56
56
  // Tennis supplies exactly one likeness, `portrait_url` (a Wikimedia
57
- // portrait), and no separate square headshot crop. Before this, the shaper
58
- // read only headshotUrl/bodyImageUrl/imageUrl/photoUrl — none of which the
59
- // tennis route emits — so every tennis profile shipped four explicit nulls
60
- // while a perfectly good photo sat one key away, and builders concluded
61
- // tennis had no images at all. The portrait fills the headshot slot too:
62
- // a card with the real photo slightly cropped beats a faceless card.
57
+ // Commons portrait), and no separate square headshot crop. Before this,
58
+ // the shaper read only headshotUrl/bodyImageUrl/imageUrl/photoUrl — none of
59
+ // which the tennis route emits — so every tennis profile shipped four
60
+ // explicit nulls while a perfectly good photo sat one key away.
63
61
  headshotUrl: pickString(r.headshotUrl, r.headshot, r.portrait_url) ?? null,
64
62
  bodyImageUrl: pickString(r.bodyImageUrl, r.fullBodyImageUrl) ?? null,
65
63
  imageUrl: pickString(r.imageUrl, r.image, r.photoUrl, r.portrait_url) ?? null,
66
64
  proxiedImageUrl: pickString(r.proxiedImageUrl, r.proxiedHeadshotUrl) ?? null,
65
+ /**
66
+ * The licence and the credit, carried with the URL.
67
+ *
68
+ * Commons portraits are mostly CC BY / CC BY-SA, which for commercial
69
+ * display generally obliges you to name the author and state the licence.
70
+ * Handing back a bare URL and letting a builder render it silently is what
71
+ * gets a product a takedown letter, so the fields a UI must show travel in
72
+ * the same object as the image. `attribution` is null for public-domain
73
+ * and CC0 images, which need no credit — that is a fact, not missing data.
74
+ * Anything without a defensible licence has its URL removed upstream, so a
75
+ * non-null headshotUrl here always has a licence beside it.
76
+ */
77
+ license: pickString(r.portrait_license) ?? null,
78
+ attribution: pickString(r.portrait_attribution) ?? null,
79
+ sourceUrl: pickString(r.portrait_source_url) ?? null,
67
80
  },
68
81
  };
69
82
  }
@@ -892,15 +892,25 @@ Example: { "game": "ufc", "q": "Jon Jones", "type": "fighter", "limit": 10 }`,
892
892
  const upstreamTotal = total;
893
893
  let droppedBelowFloor = [];
894
894
  /**
895
- * The first row the upstream returned is exempt from the floor.
895
+ * The upstream's own top ENTITY row is exempt from the floor.
896
896
  *
897
897
  * The upstream's dedicated search knows aliases the payload does not carry:
898
898
  * `q=Nole` returns Novak Djokovic first, and rankScore cannot see why, so a
899
899
  * pure floor would delete the correct answer and keep substring collisions
900
- * instead. Exactly one row is protected, and only when a query was given, so
901
- * the floor still removes all the padding it was added for.
900
+ * instead.
901
+ *
902
+ * This used to be the global items[0], which was wrong: search_entities
903
+ * merges several sources CONCURRENTLY (for tennis, /players/search and
904
+ * /competitions), so whichever promise settled first decided which row got
905
+ * protected. The live gate caught it as a flaky "first result is Jannik
906
+ * Sinner" — a competition could land at index 0 and the alias protection
907
+ * would guard the wrong thing. Only entity rows (player/team/fighter) are
908
+ * alias-matched at all; tournaments are matched by name and have no
909
+ * nicknames, so taking the first entity row is both deterministic and the
910
+ * row the rule was written for.
902
911
  */
903
- const protectedRow = q && items.length > 0 ? items[0] : null;
912
+ const ENTITY_TYPES = new Set(['player', 'team', 'fighter']);
913
+ const protectedRow = q ? items.find((it) => ENTITY_TYPES.has(it.type)) ?? null : null;
904
914
  const protectedKey = protectedRow ? `${protectedRow.game}:${protectedRow.type}:${protectedRow.id}` : null;
905
915
  if (q) {
906
916
  // Publish the score on every row, not just the ones the scored branch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cito-mcp",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Standalone MCP server for the Cito esports and sports API — 42 curated outcome tools for agents (live scoreboards, round economy, opening duels, clutches, vetoes, rosters, tennis, mma).",
5
5
  "type": "module",
6
6
  "bin": {