@claudinho/mcp 0.4.1 → 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.
Files changed (3) hide show
  1. package/README.md +36 -73
  2. package/dist/index.js +79 -51
  3. package/package.json +12 -5
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @claudinho/mcp ⚽
2
2
 
3
- **An MCP server for the 2026 men's football tournament.** Ask your agent about
4
- live scores, fixtures, group standings, and prediction-market odds — in Claude
5
- Code, Cursor, Codex, and any other MCP client.
3
+ **An MCP server for the 2026 men's football tournament.** Ask your agent about live
4
+ scores, fixtures, group standings, and the prediction-market read — in Claude Code,
5
+ Cursor, Codex, Claude Desktop, Windsurf, Zed, VS Code, or any MCP client (stdio).
6
6
 
7
7
  > ⚠️ **Not affiliated with, endorsed by, or connected to FIFA or Anthropic.**
8
8
  > Claudinho is an independent, open-source fan project. Factual match data with
@@ -15,29 +15,18 @@ Code, Cursor, Codex, and any other MCP client.
15
15
  claude mcp add claudinho -- npx -y @claudinho/mcp
16
16
  ```
17
17
 
18
- **Cursor** — add to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):
19
- ```json
20
- { "mcpServers": { "claudinho": { "command": "npx", "args": ["-y", "@claudinho/mcp"] } } }
21
- ```
22
-
23
- **Codex CLI** — add to `~/.codex/config.toml`:
24
- ```toml
25
- [mcp_servers.claudinho]
26
- command = "npx"
27
- args = ["-y", "@claudinho/mcp"]
18
+ **Codex CLI**
19
+ ```bash
20
+ codex mcp add claudinho -- npx -y @claudinho/mcp
28
21
  ```
29
- (or `codex mcp add claudinho -- npx -y @claudinho/mcp`)
30
22
 
31
- **Claude Desktop** edit `claude_desktop_config.json` (Settings Developer
32
- Edit Config), then restart Claude Desktop:
23
+ **Cursor / Claude Desktop / Windsurf / Zed / VS Code** — standard stdio config:
33
24
  ```json
34
25
  { "mcpServers": { "claudinho": { "command": "npx", "args": ["-y", "@claudinho/mcp"] } } }
35
26
  ```
36
- Config location: macOS `~/Library/Application Support/Claude/claude_desktop_config.json`,
37
- Windows `%APPDATA%\Claude\claude_desktop_config.json`.
38
-
39
- Transport is stdio, so the same package works in Windsurf, Zed, VS Code, and
40
- any other MCP client with no changes.
27
+ Cursor: `.cursor/mcp.json` (or `~/.cursor/mcp.json`). Claude Desktop: Settings →
28
+ Developer → Edit Config, then restart. Codex config file: `~/.codex/config.toml`
29
+ (`[mcp_servers.claudinho]`, `command = "npx"`, `args = ["-y", "@claudinho/mcp"]`).
41
30
 
42
31
  ## Tools
43
32
 
@@ -47,67 +36,41 @@ any other MCP client with no changes.
47
36
  | `get_live` | matches in play right now |
48
37
  | `get_match` | a single match by id |
49
38
  | `get_standings` | group table(s) — one group `A`–`L`, or all |
50
- | `get_next_fixture` | a team's next match (3-letter code, e.g. `MEX`) |
51
- | `get_market_signal` | read-only prediction-market odds for a match, a team's next fixture, or a date — informational only |
52
- | `get_share_snippet` | a copy-pasteable match-card snippet for a match, a team's next fixture, a date, or live plain text, ready to paste |
53
-
54
- All tools are **read-only** (annotated `readOnlyHint`) and accept optional
55
- `tz` (IANA timezone), `lang` (`en`/`es`/`pt`/`fr`), and `flavor`
56
- (`off`/`subtle`/`full`) arguments. Each response includes both human-readable
57
- text and structured JSON.
58
-
59
- `get_today` and `get_match` also include reliable prediction-market context when a
60
- market is available. Match→market event slugs are derived automatically, so no
61
- mapping is needed. Market data is **read-only and informational only — not betting
62
- advice** (market-implied percentages with attribution, never links or trade
63
- calls), sourced from Polymarket public data and shown only when a market maps
64
- cleanly to the result. Disable it with `CLAUDINHO_MARKETS=off`; set
65
- `CLAUDINHO_MARKETS_SOURCE=fake` (in the server `env`) for a network-free synthetic
66
- preview.
67
-
68
- `get_share_snippet` returns a polished, **copy-pasteable** match card (the same
69
- artifact as the CLI's `claudinho share`) for a match, a team's next fixture, a
70
- date, or live matches — plain text, no links, with the non-affiliation disclaimer
71
- baked in. Hand the returned `snippet` to the user as-is. `style` picks `social`
72
- (default) or `compact`; market lines (when reliable) stay **informational only**.
73
-
74
- ## Resources & prompts
75
-
76
- - Resources: `standings://{group}` (e.g. `standings://A`), `fixtures://{date}` (UTC date, e.g. `fixtures://2026-06-11`)
77
- - Prompts: `tournament_today`, `my_team`
39
+ | `get_next_fixture` | a team's next match (3-letter code, e.g. `MEX`) — fully offline |
40
+ | `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 |
41
+ | `get_share_snippet` | a copy-pasteable plain-text match card hand the returned snippet to the user as-is |
78
42
 
79
- ## Commentary flair
43
+ All tools are **read-only** (`readOnlyHint`) and accept optional `tz`, `lang`
44
+ (`en`/`es`/`pt`/`fr`), and `flavor` (`off`/`subtle`/`full`). Every response carries
45
+ human-readable text **and** structured JSON.
80
46
 
81
- By default the server adds a light, localized football-commentary voice: each
82
- match line in the text ends with a short genre-style exclamation (`— ¡GOOOOL!`),
83
- and the server instructions nudge the model to narrate scores with matching
84
- energy. The phrases are generic — no real commentator is quoted or impersonated —
85
- and they never touch the structured JSON, so the facts stay clean.
47
+ Resources: `standings://{group}`, `fixtures://{date}`. Prompts: `tournament_today`,
48
+ and `my_team` (give it a 3-letter team code; combines next fixture, standings, and
49
+ the prediction-market read).
86
50
 
87
- Control it with `CLAUDINHO_FLAVOR` (`off` | `subtle` | `full`, default `full`),
88
- or per call via the `flavor` tool argument. In Claude Code, add it to the `env`
89
- block of your server entry:
51
+ ## Market signals
90
52
 
91
- ```json
92
- { "mcpServers": { "claudinho": {
93
- "command": "npx", "args": ["-y", "@claudinho/mcp"],
94
- "env": { "CLAUDINHO_FLAVOR": "subtle" }
95
- } } }
96
- ```
53
+ Market signals are pre-match and in-play reads — finished matches never show one.
54
+ `get_today` / `get_match` include a short market line when a reliable market exists
55
+ (slugs are auto-derived per fixture; matching fails closed). **Read-only and
56
+ informational only not betting advice:** market-implied percentages with Polymarket
57
+ attribution, never links or trade calls. Disable with `CLAUDINHO_MARKETS=off`; set
58
+ `CLAUDINHO_MARKETS_SOURCE=fake` in the server `env` for a network-free, clearly
59
+ labeled synthetic preview.
97
60
 
98
- ## Other competitions
61
+ ## Commentary flair
99
62
 
100
- By default the server follows the 2026 World Cup. Set the `CLAUDINHO_COMPETITION`
101
- env var (e.g. `fifa.friendly`) to point the live tools at another
102
- competition useful for testing before the tournament. In Claude Code, add it
103
- to the `env` block of your MCP server entry.
63
+ Match lines in the text end with a short, localized, genre-style exclamation
64
+ (`— ¡GOOOOL!`) generic energy, no real commentator quoted or impersonated, never
65
+ in the structured JSON. Control with `CLAUDINHO_FLAVOR` (`off`|`subtle`|`full`,
66
+ default `full`) in the server `env`, or per call via the `flavor` argument.
104
67
 
105
68
  ## How it works
106
69
 
107
- The fixture list ships bundled in the package; only live state hits the network —
108
- live scores from **ESPN's** public scoreboard (a swappable provider, attributed
109
- in the `source` field and text) and market odds from Polymarket. The server writes
110
- nothing to stdout except the MCP protocol; diagnostics go to stderr.
70
+ All 104 fixtures ship bundled in the package; only live state hits the network —
71
+ live scores from **ESPN's** public scoreboard (swappable provider, attributed in
72
+ output), market signals from Polymarket. Stdout carries only the MCP protocol;
73
+ diagnostics go to stderr.
111
74
 
112
75
  ## License
113
76
 
package/dist/index.js CHANGED
@@ -2614,6 +2614,7 @@ function nextFixtureForTeam(code, opts = {}) {
2614
2614
  (m) => new Date(m.kickoff).getTime() >= from.getTime()
2615
2615
  );
2616
2616
  }
2617
+ var LIVE_WINDOW_MS = 140 * 6e4;
2617
2618
  function groups(fixtures = SCHEDULE) {
2618
2619
  const set = /* @__PURE__ */ new Set();
2619
2620
  for (const m of fixtures) if (m.group) set.add(m.group);
@@ -2871,6 +2872,33 @@ function shiftUtcDate(dateISO, days) {
2871
2872
  const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
2872
2873
  return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
2873
2874
  }
2875
+ var EXTRA_TIME_SLACK_MS = 60 * 6e4;
2876
+ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
2877
+ const nowMs = now.getTime();
2878
+ const candidate = fixturesByTeam(code).find((m) => {
2879
+ const k = Date.parse(m.kickoff);
2880
+ return nowMs >= k && nowMs <= k + LIVE_WINDOW_MS + EXTRA_TIME_SLACK_MS;
2881
+ });
2882
+ if (candidate) {
2883
+ const r = await getMatchById(adapter, candidate.id);
2884
+ const m = r.match ?? candidate;
2885
+ if (!isFinished(m.status)) return { ...r, match: m };
2886
+ }
2887
+ const next = nextFixtureForTeam(code, { from: now });
2888
+ return { match: next, degraded: false };
2889
+ }
2890
+ async function getMatchById(adapter, id) {
2891
+ const base = allFixtures().find((m) => m.id === id);
2892
+ if (!base) return { match: void 0, degraded: false };
2893
+ const day = base.kickoff.slice(0, 10);
2894
+ try {
2895
+ const live = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchByDate(day);
2896
+ const hit = live.find((m) => m.id === id);
2897
+ return { match: hit ?? base, degraded: false, source: hit ? adapter.name : void 0 };
2898
+ } catch {
2899
+ return { match: base, degraded: true };
2900
+ }
2901
+ }
2874
2902
  async function getLiveMatches(adapter) {
2875
2903
  try {
2876
2904
  return { matches: await adapter.fetchLive(), degraded: false, source: adapter.name };
@@ -2879,6 +2907,12 @@ async function getLiveMatches(adapter) {
2879
2907
  }
2880
2908
  }
2881
2909
  var DEFAULT_MAX_AGE_MS = 15 * 6e4;
2910
+ function marketRelevant(match, now = /* @__PURE__ */ new Date()) {
2911
+ if (isLive(match.status)) return true;
2912
+ if (isFinished(match.status)) return false;
2913
+ const k = Date.parse(match.kickoff);
2914
+ return !Number.isFinite(k) || now.getTime() <= k + LIVE_WINDOW_MS;
2915
+ }
2882
2916
  function normalizeOutcomes(outcomes) {
2883
2917
  const sum = outcomes.reduce(
2884
2918
  (s, o) => s + (Number.isFinite(o.probability) && o.probability > 0 ? o.probability : 0),
@@ -3454,13 +3488,19 @@ function resolveMarketProvider(args) {
3454
3488
  function marketDisplayable(sig) {
3455
3489
  return !sig.ambiguous && sig.favorite != null && hasSaneDistribution(sig.outcomes);
3456
3490
  }
3457
- function marketHeader(m) {
3458
- return `${m.home.flag} ${m.home.name} vs ${m.away.name} ${m.away.flag}`;
3491
+ function marketHeader(m, args) {
3492
+ const when = formatDate(m.kickoff, { tz: args.tz, locale: args.lang });
3493
+ return `${m.home.flag} ${m.home.name} vs ${m.away.name} ${m.away.flag} (${when})`;
3459
3494
  }
3460
- function marketText(m, sig) {
3461
- return `${marketHeader(m)}
3495
+ function marketText(m, sig, args) {
3496
+ return `${marketHeader(m, args)}
3462
3497
  ${marketBlock(sig, m).join("\n")}`;
3463
3498
  }
3499
+ function noSignalText(m, args, now) {
3500
+ if (marketRelevant(m, now)) return `No reliable market signal for ${marketHeader(m, args)}.`;
3501
+ const verb = isFinished(m.status) ? "has finished" : "appears to have finished";
3502
+ return `${marketHeader(m, args)} ${verb} \u2014 market signals are pre-match and in-play reads.`;
3503
+ }
3464
3504
  function marketData(sig) {
3465
3505
  return {
3466
3506
  matchId: sig.matchId,
@@ -3521,10 +3561,12 @@ async function cachedMarketSignals(args, matches) {
3521
3561
  }
3522
3562
  async function reliableMarketData(args, matches) {
3523
3563
  if (!marketsEnabled()) return void 0;
3524
- const signals = await cachedMarketSignals(args, matches);
3525
- const now = /* @__PURE__ */ new Date();
3564
+ const now = args.now ?? /* @__PURE__ */ new Date();
3565
+ const relevant = matches.filter((m) => marketRelevant(m, now));
3566
+ if (relevant.length === 0) return void 0;
3567
+ const signals = await cachedMarketSignals(args, relevant);
3526
3568
  const out = {};
3527
- for (const m of matches) {
3569
+ for (const m of relevant) {
3528
3570
  const s = signals.get(m.id);
3529
3571
  if (s && isReliableMarketSignal(s, { now })) out[m.id] = marketData(s);
3530
3572
  }
@@ -3577,27 +3619,16 @@ ${matchList(matches, "No matches in play right now.", opts)}`;
3577
3619
  };
3578
3620
  }
3579
3621
  async function toolGetMatch(args) {
3580
- let match = allFixtures().find((m) => m.id === args.id);
3581
- let degraded = false;
3582
- let liveSource;
3583
- if (match) {
3584
- try {
3585
- const adapter = resolveAdapter(args);
3586
- const live = await adapter.fetchByDate(match.kickoff.slice(0, 10));
3587
- match = live.find((m) => m.id === args.id) ?? match;
3588
- liveSource = adapter.name;
3589
- } catch {
3590
- degraded = true;
3591
- }
3592
- }
3622
+ const { match, degraded, source: liveSource } = await getMatchById(resolveAdapter(args), args.id);
3593
3623
  if (!match) {
3594
3624
  return { text: withDisclaimer(`No match found with id ${args.id}.`), data: { match: null } };
3595
3625
  }
3596
3626
  const opts = fmtOpts(args);
3627
+ const now = args.now ?? /* @__PURE__ */ new Date();
3597
3628
  let marketSignal;
3598
- if (marketsEnabled()) {
3629
+ if (marketsEnabled() && marketRelevant(match, now)) {
3599
3630
  const s = (await cachedMarketSignals(args, [match])).get(match.id);
3600
- if (s && isReliableMarketSignal(s, { now: /* @__PURE__ */ new Date() })) marketSignal = s;
3631
+ if (s && isReliableMarketSignal(s, { now })) marketSignal = s;
3601
3632
  }
3602
3633
  const base = matchLine(match, opts);
3603
3634
  const text = marketSignal ? `${base}
@@ -3656,11 +3687,13 @@ ${matchLine(fixture, opts)}`),
3656
3687
  }
3657
3688
  async function toolGetMarketSignal(args) {
3658
3689
  const provider = resolveMarketProvider(args);
3690
+ const now = args.now ?? /* @__PURE__ */ new Date();
3659
3691
  if (args.matchId) {
3660
- const match = allFixtures().find((m) => m.id === args.matchId);
3661
- const sig = match ? await getMarketSignal(provider, match) : void 0;
3692
+ const { match } = await getMatchById(resolveAdapter(args), args.matchId);
3693
+ const relevant = match ? marketRelevant(match, now) : false;
3694
+ const sig = match && relevant ? await getMarketSignal(provider, match) : void 0;
3662
3695
  const shown2 = match && sig && marketDisplayable(sig) ? sig : void 0;
3663
- const text2 = !match ? `No match found with id ${args.matchId}.` : shown2 ? marketText(match, shown2) : `No reliable market signal for ${marketHeader(match)}.`;
3696
+ const text2 = !match ? `No match found with id ${args.matchId}.` : shown2 ? marketText(match, shown2, args) : noSignalText(match, args, now);
3664
3697
  return {
3665
3698
  text: withDisclaimer(text2),
3666
3699
  data: {
@@ -3672,10 +3705,11 @@ async function toolGetMarketSignal(args) {
3672
3705
  }
3673
3706
  if (args.team) {
3674
3707
  const code = args.team.toUpperCase();
3675
- const fixture = nextFixtureForTeam(code);
3676
- const sig = fixture ? await getMarketSignal(provider, fixture) : void 0;
3708
+ const { match: fixture } = await marketFixtureForTeam(resolveAdapter(args), code, now);
3709
+ const relevant = fixture ? marketRelevant(fixture, now) : false;
3710
+ const sig = fixture && relevant ? await getMarketSignal(provider, fixture) : void 0;
3677
3711
  const shown2 = fixture && sig && marketDisplayable(sig) ? sig : void 0;
3678
- const text2 = !fixture ? `No upcoming fixture found for ${code}.` : shown2 ? marketText(fixture, shown2) : `No reliable market signal for ${code}'s next fixture.`;
3712
+ const text2 = !fixture ? `No upcoming fixture found for ${code}.` : shown2 ? marketText(fixture, shown2, args) : noSignalText(fixture, args, now);
3679
3713
  return {
3680
3714
  text: withDisclaimer(text2),
3681
3715
  data: {
@@ -3686,15 +3720,15 @@ async function toolGetMarketSignal(args) {
3686
3720
  }
3687
3721
  };
3688
3722
  }
3689
- const date = args.date ?? localDate((/* @__PURE__ */ new Date()).toISOString(), args.tz);
3723
+ const date = args.date ?? localDate(now.toISOString(), args.tz);
3690
3724
  const { matches } = await getMatchesForDate(resolveAdapter(args), date);
3691
- const todays = fixturesByDate(date, matches, args.tz);
3725
+ const todays = fixturesByDate(date, matches, args.tz).filter((m) => marketRelevant(m, now));
3692
3726
  const { signals } = await getMarketSignals(provider, todays, MARKETS_TOOL_OPTS);
3693
3727
  const shown = todays.map((m) => ({ match: m, signal: signals.get(m.id) })).filter(
3694
3728
  (r) => !!r.signal && marketDisplayable(r.signal)
3695
3729
  );
3696
3730
  const text = shown.length ? `Market signals on ${date}:
3697
- ${shown.map(({ match, signal }) => marketText(match, signal)).join("\n\n")}` : `No reliable market signals on ${date}.`;
3731
+ ${shown.map(({ match, signal }) => marketText(match, signal, args)).join("\n\n")}` : `No reliable market signals on ${date}.`;
3698
3732
  return {
3699
3733
  text: withDisclaimer(text),
3700
3734
  data: {
@@ -3706,10 +3740,12 @@ ${shown.map(({ match, signal }) => marketText(match, signal)).join("\n\n")}` : `
3706
3740
  }
3707
3741
  async function reliableSignalMap(args, matches) {
3708
3742
  if (!marketsEnabled()) return /* @__PURE__ */ new Map();
3709
- const signals = await cachedMarketSignals(args, matches);
3710
- const now = /* @__PURE__ */ new Date();
3743
+ const now = args.now ?? /* @__PURE__ */ new Date();
3744
+ const relevant = matches.filter((m) => marketRelevant(m, now));
3745
+ if (relevant.length === 0) return /* @__PURE__ */ new Map();
3746
+ const signals = await cachedMarketSignals(args, relevant);
3711
3747
  const out = /* @__PURE__ */ new Map();
3712
- for (const m of matches) {
3748
+ for (const m of relevant) {
3713
3749
  const s = signals.get(m.id);
3714
3750
  if (s && isReliableMarketSignal(s, { now }) && marketDisplayable(s)) out.set(m.id, s);
3715
3751
  }
@@ -3771,17 +3807,7 @@ async function toolGetShareSnippet(args) {
3771
3807
  );
3772
3808
  }
3773
3809
  if (args.matchId) {
3774
- let match = allFixtures().find((m) => m.id === args.matchId);
3775
- let source2;
3776
- if (match) {
3777
- try {
3778
- const adapter = resolveAdapter(args);
3779
- const live = await adapter.fetchByDate(match.kickoff.slice(0, 10));
3780
- match = live.find((m) => m.id === args.matchId) ?? match;
3781
- source2 = adapter.name;
3782
- } catch {
3783
- }
3784
- }
3810
+ const { match, source: source2 } = await getMatchById(resolveAdapter(args), args.matchId);
3785
3811
  const matches = match ? [match] : [];
3786
3812
  return shareResult(
3787
3813
  "match",
@@ -3845,12 +3871,12 @@ async function toolGetShareSnippet(args) {
3845
3871
 
3846
3872
  // src/server.ts
3847
3873
  var SERVER_NAME = "claudinho";
3848
- var SERVER_VERSION = "0.4.1";
3874
+ var SERVER_VERSION = "0.4.3";
3849
3875
  var VOICE = asFlavorLevel(process.env.CLAUDINHO_FLAVOR) === "off" ? "" : `
3850
3876
  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.`;
3851
3877
  var INSTRUCTIONS = `Claudinho serves live scores, fixtures, and group standings for the 2026 men's football tournament.
3852
3878
  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), and get_standings for group tables.
3853
- Use get_market_signal for read-only prediction-market odds (a match, a team's next fixture, or a date). Market data is informational only \u2014 relay the percentages factually and never frame it as betting or trading advice.
3879
+ 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.
3854
3880
  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}
3855
3881
  ${DISCLAIMER}`;
3856
3882
  var dateArg = z.string().refine(isValidDate, "must be a real calendar date in YYYY-MM-DD form");
@@ -3926,7 +3952,7 @@ function buildServer() {
3926
3952
  "get_next_fixture",
3927
3953
  {
3928
3954
  title: "Next fixture for a team",
3929
- description: "A team's next scheduled match. Use a 3-letter code, e.g. MEX, BRA, USA.",
3955
+ description: "A team's next scheduled match. Use a 3-letter code, e.g. MEX, BRA, USA. Instant and offline \u2014 answered from the bundled schedule, no network.",
3930
3956
  inputSchema: { team: teamArg.describe("3-letter team code, e.g. MEX"), ...commonArgs },
3931
3957
  // Read-only and served entirely from the bundled static schedule.
3932
3958
  annotations: { readOnlyHint: true, openWorldHint: false }
@@ -3937,10 +3963,12 @@ function buildServer() {
3937
3963
  "get_market_signal",
3938
3964
  {
3939
3965
  title: "Prediction-market signal",
3940
- description: "Read-only prediction-market odds for a match (by id), a team's next fixture, or a date (default: today). Returns market-implied percentages with attribution. Informational only \u2014 relay the numbers factually; do not add betting, trading, or 'value' advice, and do not invent links.",
3966
+ description: "Read-only prediction-market signals for a match (by id), a team's current-or-next fixture, or a date (default: today). Returns market-implied percentages with attribution. Shown only before and during a match \u2014 finished matches have no market read. Informational only \u2014 relay the numbers factually; do not add betting, trading, or 'value' advice, and do not invent links.",
3941
3967
  inputSchema: {
3942
3968
  matchId: z.string().optional().describe("Match id (most specific)"),
3943
- team: teamArg.optional().describe("3-letter team code for that team's next fixture, e.g. MEX"),
3969
+ team: teamArg.optional().describe(
3970
+ "3-letter team code, e.g. MEX \u2014 resolves to the team's in-play match when one is live, else their next fixture"
3971
+ ),
3944
3972
  date: dateArg.optional().describe("Date as YYYY-MM-DD (default: today) for all that day's signals"),
3945
3973
  ...commonArgs
3946
3974
  },
@@ -4032,7 +4060,7 @@ function buildServer() {
4032
4060
  role: "user",
4033
4061
  content: {
4034
4062
  type: "text",
4035
- text: `Using get_next_fixture, get_standings, and get_market_signal, tell me about ${team}'s next match in the 2026 tournament, their current group standing, and what prediction markets currently say about that match. Treat the market percentages as informational context only \u2014 relay them factually, never as betting or trading advice.`
4063
+ text: `Using get_next_fixture, get_standings, and get_market_signal, tell me about ${team}'s next match in the 2026 tournament, their current group standing, and what prediction markets currently say about that match. Always state each fixture's date so a market read is never mistaken for a different match. Treat the market percentages as informational context only \u2014 relay them factually, never as betting or trading advice.`
4036
4064
  }
4037
4065
  }
4038
4066
  ]
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@claudinho/mcp",
3
- "version": "0.4.1",
4
- "description": "Claudinho MCP server — ask your agent about the 2026 men's football tournament: live scores, fixtures, standings, prediction-market odds. Not affiliated with FIFA or Anthropic.",
3
+ "version": "0.4.3",
4
+ "mcpName": "io.github.arturogarrido/claudinho",
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.",
5
6
  "type": "module",
6
7
  "license": "MIT",
7
8
  "author": "Arturo Garrido",
@@ -30,12 +31,18 @@
30
31
  ],
31
32
  "keywords": [
32
33
  "mcp",
34
+ "mcp-server",
33
35
  "model-context-protocol",
36
+ "claude-code",
37
+ "cursor",
38
+ "world-cup",
39
+ "world-cup-2026",
34
40
  "football",
35
41
  "soccer",
36
- "world-cup",
37
- "2026",
38
42
  "live-scores",
43
+ "fixtures",
44
+ "standings",
45
+ "2026",
39
46
  "vibinglavidaloca"
40
47
  ],
41
48
  "dependencies": {
@@ -47,7 +54,7 @@
47
54
  "tsup": "^8.0.0",
48
55
  "typescript": "^5.7.0",
49
56
  "vitest": "^4.1.0",
50
- "@claudinho/core": "0.4.1"
57
+ "@claudinho/core": "0.4.3"
51
58
  },
52
59
  "scripts": {
53
60
  "build": "tsup",