cito-mcp 0.3.18 → 0.3.19

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.
Files changed (2) hide show
  1. package/dist/tools/team.js +44 -27
  2. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * team_profile + head_to_head
3
3
  */
4
- import { clampInt, extractRows, fetchJson, gameNotIncludedHint, asRecord, pickString, } from '../client.js';
4
+ import { clampInt, extractRows, fetchJson, gameNotIncludedHint, asRecord, pickString, unwrapPayload, } from '../client.js';
5
5
  import { errorEnvelope, mapHttpToCode, newRequestId, partialFromRejection, successEnvelope, } from '../envelope.js';
6
6
  import { normalizeMatch } from './normalize.js';
7
7
  import { boolSchema, gameSchema, isPrimaryGame, limitSchema, parseGame, PRIMARY_GAMES, stringSchema, } from './types.js';
@@ -24,22 +24,36 @@ function teamIdentity(game, raw, idHint, slugHint) {
24
24
  name,
25
25
  game,
26
26
  region: pickString(nested.region, nested.country) ?? null,
27
+ // CS2 publishes countryName/countryCode and worldRanking on the team
28
+ // resource and none of it reached this card: a team page rendered with no
29
+ // flag and no ladder position while the API had both. Spirit came back as
30
+ // {id, slug, name, game, region} with world #1 nowhere in sight.
31
+ country: pickString(nested.countryName, nested.country, nested.countryCode) ?? null,
32
+ countryCode: pickString(nested.countryCode) ?? null,
33
+ worldRanking: numberOrNull(nested.worldRanking ?? nested.ranking ?? nested.rank),
27
34
  };
28
35
  }
36
+ /** A finite number, or null. Ranks arrive as number or numeric string. */
37
+ function numberOrNull(value) {
38
+ if (value === null || value === undefined || value === '')
39
+ return null;
40
+ const parsed = Number(value);
41
+ return Number.isFinite(parsed) ? parsed : null;
42
+ }
29
43
  export const teamProfile = {
30
44
  name: 'team_profile',
31
- description: `Team/org card: identity, roster, recent matches, and form/trends/radar when available.
32
-
33
- When to use:
34
- - Team page / "who is on this roster?"
35
- - Builder team screen sample
36
-
37
- Prefer over: separate roster + matches + detail via call_api.
38
-
39
- Do not use when: UFC fighters → player_profile; unknown name → resolve_entity first.
40
- Dota may return partial roster (API gap). Prefer slug for lol/cod; teamId for cs2.
41
-
42
- Parallel-safe: yes. Upstream cost: 2–4.
45
+ description: `Team/org card: identity, roster, recent matches, and form/trends/radar when available.
46
+
47
+ When to use:
48
+ - Team page / "who is on this roster?"
49
+ - Builder team screen sample
50
+
51
+ Prefer over: separate roster + matches + detail via call_api.
52
+
53
+ Do not use when: UFC fighters → player_profile; unknown name → resolve_entity first.
54
+ Dota may return partial roster (API gap). Prefer slug for lol/cod; teamId for cs2.
55
+
56
+ Parallel-safe: yes. Upstream cost: 2–4.
43
57
  Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
44
58
  inputSchema: {
45
59
  type: 'object',
@@ -374,8 +388,11 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
374
388
  const res = await fetchJson(ctx, `/cs2/teams/${encodeURIComponent(tid)}/trends`);
375
389
  upstreamCalls += 1;
376
390
  rateLimit = { ...rateLimit, ...res.headers };
391
+ // res.data is the whole upstream body, so this nested
392
+ // {success, data:{...}} inside our own envelope. Every other field
393
+ // here is normalized; this one leaked the REST wrapper to the agent.
377
394
  if (res.ok)
378
- form = res.data;
395
+ form = unwrapPayload(res.data) ?? res.data;
379
396
  else {
380
397
  partial.push(partialFromRejection('form', {
381
398
  code: mapHttpToCode(res.status),
@@ -545,19 +562,19 @@ function winnerSide(match, a) {
545
562
  }
546
563
  export const headToHead = {
547
564
  name: 'head_to_head',
548
- description: `Composed head-to-head record between two teams, two UFC fighters, or two tennis players. No first-class REST H2H exists — this tool filters match history server-side.
549
-
550
- When to use:
551
- - Rivalry / series record questions
552
- - Supporting context for previews
553
-
554
- Prefer over: agent-side double match-list filtering.
555
-
556
- Do not use when: single-side form only → team_profile or player_profile.
557
-
558
- Caveat: Dota filters are weaker; expect meta.warnings when data is sparse.
559
-
560
- Parallel-safe: yes. Upstream cost: 2–4.
565
+ description: `Composed head-to-head record between two teams, two UFC fighters, or two tennis players. No first-class REST H2H exists — this tool filters match history server-side.
566
+
567
+ When to use:
568
+ - Rivalry / series record questions
569
+ - Supporting context for previews
570
+
571
+ Prefer over: agent-side double match-list filtering.
572
+
573
+ Do not use when: single-side form only → team_profile or player_profile.
574
+
575
+ Caveat: Dota filters are weaker; expect meta.warnings when data is sparse.
576
+
577
+ Parallel-safe: yes. Upstream cost: 2–4.
561
578
  Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
562
579
  inputSchema: {
563
580
  type: 'object',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cito-mcp",
3
- "version": "0.3.18",
3
+ "version": "0.3.19",
4
4
  "description": "Standalone MCP server for the Cito esports API — 15 curated outcome tools for agents (live, schedule, profiles, standings, previews, event cards).",
5
5
  "type": "module",
6
6
  "bin": {