@claudinho/mcp 0.8.17 → 0.8.18

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
@@ -51,9 +51,11 @@ Then just ask your agent naturally — it picks the right tool and answers with
51
51
  | `get_next_fixture` | a team's next match (3-letter code, e.g. `MEX`) — live-resolves a confirmed knockout tie from the feed; group fixtures offline, fails back to the bundled schedule if the feed is down |
52
52
  | `get_market_signal` | read-only prediction-market signal for a match, a team's current-or-next fixture (in-play preferred while live), or a date — informational only |
53
53
  | `get_share_snippet` | a copy-pasteable plain-text card — for a match, a team's next fixture, a group's standings table (`group`), the knockout bracket (`bracket: true`, optional `knockoutStage`), a date, or live — hand the returned snippet to the user as-is |
54
+ | `get_team` | resolve a nation name or code to its FIFA 3-letter code, flag, and group — fuzzy (`Mexico`, `mex`, `DR Congo`, `Türkiye`); call it first to turn a user's team name into the code the other tools need. Offline (no network) |
54
55
 
55
- All tools are **read-only** (`readOnlyHint`) and accept optional `tz`, `lang`
56
- (`en`/`es`/`pt`/`fr`), and `flavor` (`off`/`subtle`/`full`). Every response carries
56
+ Most tools are **read-only** (`readOnlyHint`) and accept optional `tz`, `lang`
57
+ (`en`/`es`/`pt`/`fr`), and `flavor` (`off`/`subtle`/`full`); `get_team` is read-only
58
+ **and** offline. Every response carries
57
59
  human-readable text **and** structured content, validated against each tool's
58
60
  declared `outputSchema`.
59
61
 
package/dist/index.js CHANGED
@@ -2802,6 +2802,60 @@ function groups(fixtures = SCHEDULE) {
2802
2802
  for (const m of fixtures) if (m.group) set.add(m.group);
2803
2803
  return [...set].sort();
2804
2804
  }
2805
+ function norm2(s) {
2806
+ return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z]/g, "");
2807
+ }
2808
+ function allTeams(fixtures = allFixtures()) {
2809
+ const seen = /* @__PURE__ */ new Map();
2810
+ for (const m of fixtures) {
2811
+ if (m.stage !== "GROUP") continue;
2812
+ for (const t2 of [m.home, m.away]) {
2813
+ if (!/^[A-Z]{3}$/.test(t2.code) || t2.flag === "\u{1F3F3}\uFE0F") continue;
2814
+ if (!seen.has(t2.code)) seen.set(t2.code, { ...t2, group: m.group ?? void 0 });
2815
+ }
2816
+ }
2817
+ return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
2818
+ }
2819
+ var TEAM_ALIASES = {
2820
+ turkey: "TUR",
2821
+ holland: "NED",
2822
+ korea: "KOR",
2823
+ skorea: "KOR",
2824
+ korearepublic: "KOR",
2825
+ republicofkorea: "KOR",
2826
+ czechrepublic: "CZE",
2827
+ congo: "COD",
2828
+ drcongo: "COD",
2829
+ drc: "COD",
2830
+ democraticrepublicofcongo: "COD",
2831
+ democraticrepublicofthecongo: "COD",
2832
+ cotedivoire: "CIV",
2833
+ caboverde: "CPV",
2834
+ bosnia: "BIH",
2835
+ bosniaandherzegovina: "BIH",
2836
+ us: "USA",
2837
+ america: "USA",
2838
+ unitedstatesofamerica: "USA"
2839
+ };
2840
+ function lookupTeam(query, fixtures = allFixtures()) {
2841
+ const roster = allTeams(fixtures);
2842
+ const q = norm2(query);
2843
+ if (!q) return { query, team: null, matches: [] };
2844
+ const byCode = roster.find((t2) => norm2(t2.code) === q);
2845
+ if (byCode) return { query, team: byCode, matches: [byCode] };
2846
+ const byName = roster.find((t2) => norm2(t2.name) === q);
2847
+ if (byName) return { query, team: byName, matches: [byName] };
2848
+ const aliasCode = TEAM_ALIASES[q];
2849
+ if (aliasCode) {
2850
+ const t2 = roster.find((r) => r.code === aliasCode);
2851
+ if (t2) return { query, team: t2, matches: [t2] };
2852
+ }
2853
+ if (q.length < 3) return { query, team: null, matches: [] };
2854
+ const prefix = roster.filter((t2) => norm2(t2.name).startsWith(q));
2855
+ const substr = roster.filter((t2) => norm2(t2.name).includes(q) && !prefix.includes(t2));
2856
+ const matches = [...prefix, ...substr];
2857
+ return { query, team: matches.length === 1 ? matches[0] : null, matches };
2858
+ }
2805
2859
  function blankRow(team) {
2806
2860
  return {
2807
2861
  team,
@@ -4884,6 +4938,19 @@ ${matchLine(fixture, opts)}`, source, args.lang),
4884
4938
  data: { team: code, fixture, degraded }
4885
4939
  };
4886
4940
  }
4941
+ function toolGetTeam(args) {
4942
+ const { team, matches } = lookupTeam(args.query ?? "");
4943
+ const data = { query: args.query ?? "", team: team ?? null, matches, count: matches.length };
4944
+ let text;
4945
+ if (team) {
4946
+ text = `${team.code} \u2014 ${team.flag} ${team.name}${team.group ? ` \xB7 Group ${team.group}` : ""}`;
4947
+ } else if (matches.length > 0) {
4948
+ text = `"${args.query}" is ambiguous. Did you mean: ${matches.map((t2) => `${t2.name} (${t2.code})`).join(", ")}?`;
4949
+ } else {
4950
+ text = `No team found for "${args.query}". Use a nation name or 3-letter code (e.g. Mexico, MEX).`;
4951
+ }
4952
+ return { text: withDisclaimer(text), data };
4953
+ }
4887
4954
  async function toolGetMarketSignal(args) {
4888
4955
  const provider = resolveMarketProvider(args);
4889
4956
  const now = args.now ?? /* @__PURE__ */ new Date();
@@ -5152,11 +5219,12 @@ async function toolGetShareSnippet(args) {
5152
5219
 
5153
5220
  // src/server.ts
5154
5221
  var SERVER_NAME = "claudinho";
5155
- var SERVER_VERSION = "0.8.17";
5222
+ var SERVER_VERSION = "0.8.18";
5156
5223
  var VOICE = asFlavorLevel(process.env.CLAUDINHO_FLAVOR) === "off" ? "" : `
5157
5224
  Voice: when relaying scores, narrate with lively, regionally-appropriate football-commentary energy in the user's language. Each match line may end with a short exclamation ("\u2014 \xA1GOOOOL!") \u2014 use it as a tone cue. Keep every fact exact; never invent details and never impersonate or name a real commentator.`;
5158
5225
  var INSTRUCTIONS = `Claudinho serves live scores, fixtures, and group standings for the 2026 men's football tournament.
5159
- Use get_live during matches, get_today for a day's schedule, get_next_fixture for a specific team (3-letter code, e.g. MEX), get_standings for group tables, and get_bracket for the knockout tree.
5226
+ The team-taking tools (get_next_fixture, get_market_signal, get_share_snippet) expect a 3-letter code (e.g. MEX). When the user gives a nation NAME, call get_team FIRST to resolve it \u2014 get_team is fuzzy ("Mexico", "DR Congo", "T\xFCrkiye"), offline, and returns candidates when the name is ambiguous.
5227
+ Use get_live during matches, get_today for a day's schedule, get_next_fixture for a specific team, get_standings for group tables, and get_bracket for the knockout tree.
5160
5228
  Use get_market_signal for read-only prediction-market signals (a match, a team's current-or-next fixture, or a date). Market data is informational only \u2014 relay the percentages factually and never frame it as betting or trading advice.
5161
5229
  Use get_share_snippet to produce a ready-to-paste match card (for a match, a team's next fixture, a date, or live matches) \u2014 hand the user the returned snippet text verbatim.${VOICE}
5162
5230
  ${DISCLAIMER}`;
@@ -5240,6 +5308,13 @@ var shareOut = {
5240
5308
  matches: z.array(matchOut).optional(),
5241
5309
  marketSignals: z.record(anyObj).optional()
5242
5310
  };
5311
+ var teamInfo = z.object({ code: z.string(), name: z.string(), flag: z.string(), group: z.string() }).partial().passthrough();
5312
+ var teamOut = {
5313
+ query: z.string(),
5314
+ team: teamInfo.nullable(),
5315
+ matches: z.array(teamInfo),
5316
+ count: z.number()
5317
+ };
5243
5318
  function toContent(r) {
5244
5319
  return {
5245
5320
  content: [
@@ -5375,6 +5450,20 @@ function buildServer() {
5375
5450
  },
5376
5451
  async (args) => toContent(await toolGetShareSnippet(args))
5377
5452
  );
5453
+ server.registerTool(
5454
+ "get_team",
5455
+ {
5456
+ title: "Resolve a team",
5457
+ description: `Resolve a nation name or 3-letter code to its FIFA code, flag, and group. Fuzzy and forgiving: accepts "Mexico", "mex", "USA", "DR Congo", "T\xFCrkiye"/"Turkey", "Holland", etc. Use this FIRST to turn a user's team name into the code the other tools need (get_next_fixture, get_standings, get_market_signal, get_share_snippet). Returns the single confident match (team), plus candidates (matches) when the query is ambiguous (e.g. "south" \u2192 South Africa, South Korea). Offline \u2014 reads the bundled roster, never the network.`,
5458
+ inputSchema: {
5459
+ query: z.string().describe('Team name or 3-letter code, e.g. "Mexico", "MEX", "DR Congo"')
5460
+ },
5461
+ // Read-only AND offline — resolves against the bundled roster, no provider call.
5462
+ annotations: { readOnlyHint: true, openWorldHint: false },
5463
+ outputSchema: teamOut
5464
+ },
5465
+ async (args) => toContent(toolGetTeam(args))
5466
+ );
5378
5467
  server.registerResource(
5379
5468
  "standings",
5380
5469
  new ResourceTemplate("standings://{group}", { list: void 0 }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudinho/mcp",
3
- "version": "0.8.17",
3
+ "version": "0.8.18",
4
4
  "mcpName": "io.github.arturogarrido/claudinho",
5
5
  "description": "MCP server for the 2026 men's football tournament — live scores, fixtures, standings, read-only prediction-market signals, and paste-ready match cards. Works with Claude Code, Cursor, Codex, Windsurf, Zed. Not affiliated with FIFA or Anthropic.",
6
6
  "type": "module",
@@ -57,7 +57,7 @@
57
57
  "tsup": "^8.0.0",
58
58
  "typescript": "^5.7.0",
59
59
  "vitest": "^4.1.9",
60
- "@claudinho/core": "0.8.17"
60
+ "@claudinho/core": "0.8.18"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "tsup",