@claudinho/mcp 0.4.3 → 0.5.1
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 +2 -2
- package/dist/index.js +194 -58
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,10 +35,10 @@ Developer → Edit Config, then restart. Codex config file: `~/.codex/config.tom
|
|
|
35
35
|
| `get_today` | fixtures for a date (default: today), grouped in the caller's `tz`, live scores overlaid |
|
|
36
36
|
| `get_live` | matches in play right now |
|
|
37
37
|
| `get_match` | a single match by id |
|
|
38
|
-
| `get_standings` | group table(s) — one group `A`–`L`, or all |
|
|
38
|
+
| `get_standings` | live cumulative group table(s) — one group `A`–`L`, or all |
|
|
39
39
|
| `get_next_fixture` | a team's next match (3-letter code, e.g. `MEX`) — fully offline |
|
|
40
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
|
|
41
|
+
| `get_share_snippet` | a copy-pasteable plain-text card — for a match, a team's next fixture, a group's standings table (`group`), a date, or live — hand the returned snippet to the user as-is |
|
|
42
42
|
|
|
43
43
|
All tools are **read-only** (`readOnlyHint`) and accept optional `tz`, `lang`
|
|
44
44
|
(`en`/`es`/`pt`/`fr`), and `flavor` (`off`/`subtle`/`full`). Every response carries
|
package/dist/index.js
CHANGED
|
@@ -2760,6 +2760,43 @@ function mapEspnEvent(ev, ctx = {}) {
|
|
|
2760
2760
|
function toEspnDate(d) {
|
|
2761
2761
|
return d.replace(/\D/g, "").slice(0, 8);
|
|
2762
2762
|
}
|
|
2763
|
+
function statVal(stats, name) {
|
|
2764
|
+
const v = stats?.find((s) => s.name === name)?.value;
|
|
2765
|
+
return typeof v === "number" && Number.isFinite(v) ? Math.round(v) : 0;
|
|
2766
|
+
}
|
|
2767
|
+
function entryToRow(e) {
|
|
2768
|
+
return {
|
|
2769
|
+
team: toTeam(e.team),
|
|
2770
|
+
played: statVal(e.stats, "gamesPlayed"),
|
|
2771
|
+
won: statVal(e.stats, "wins"),
|
|
2772
|
+
drawn: statVal(e.stats, "ties"),
|
|
2773
|
+
lost: statVal(e.stats, "losses"),
|
|
2774
|
+
goalsFor: statVal(e.stats, "pointsFor"),
|
|
2775
|
+
goalsAgainst: statVal(e.stats, "pointsAgainst"),
|
|
2776
|
+
goalDiff: statVal(e.stats, "pointDifferential"),
|
|
2777
|
+
points: statVal(e.stats, "points")
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
function parseStandings(data) {
|
|
2781
|
+
const out = [];
|
|
2782
|
+
for (const child of data.children ?? []) {
|
|
2783
|
+
const letter = (child.name ?? child.abbreviation ?? "").match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
|
|
2784
|
+
if (!letter) continue;
|
|
2785
|
+
const ranked = (child.standings?.entries ?? []).map((e) => ({
|
|
2786
|
+
row: entryToRow(e),
|
|
2787
|
+
rank: statVal(e.stats, "rank")
|
|
2788
|
+
}));
|
|
2789
|
+
ranked.sort((a, b) => {
|
|
2790
|
+
if (a.rank && b.rank && a.rank !== b.rank) return a.rank - b.rank;
|
|
2791
|
+
const r = a.row;
|
|
2792
|
+
const s = b.row;
|
|
2793
|
+
return s.points - r.points || s.goalDiff - r.goalDiff || s.goalsFor - r.goalsFor || r.team.code.localeCompare(s.team.code);
|
|
2794
|
+
});
|
|
2795
|
+
out.push({ group: letter, rows: ranked.map((x) => x.row) });
|
|
2796
|
+
}
|
|
2797
|
+
out.sort((a, b) => a.group.localeCompare(b.group));
|
|
2798
|
+
return out;
|
|
2799
|
+
}
|
|
2763
2800
|
var EspnAdapter = class {
|
|
2764
2801
|
constructor(opts = {}) {
|
|
2765
2802
|
this.opts = opts;
|
|
@@ -2779,25 +2816,30 @@ var EspnAdapter = class {
|
|
|
2779
2816
|
const today = await this.fetchScoreboard();
|
|
2780
2817
|
return today.filter((m) => m.status === "LIVE" || m.status === "HT");
|
|
2781
2818
|
}
|
|
2819
|
+
/** Standings endpoint URL (lives under apis/v2, not site/v2; derived from base). */
|
|
2820
|
+
standingsUrl() {
|
|
2821
|
+
const base = this.opts.baseUrl ?? DEFAULT_BASE;
|
|
2822
|
+
return `${base.replace("/apis/site/v2/", "/apis/v2/")}/standings`;
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Authoritative, cumulative group tables from the standings endpoint. Throws
|
|
2826
|
+
* on fetch/parse failure (the caller decides the fallback). Group-stage only:
|
|
2827
|
+
* non-group `children` are filtered out by {@link parseStandings}.
|
|
2828
|
+
*/
|
|
2829
|
+
async fetchStandings() {
|
|
2830
|
+
return parseStandings(await this.get(this.standingsUrl()));
|
|
2831
|
+
}
|
|
2782
2832
|
/**
|
|
2783
2833
|
* Build (and cache) a team-code -> group-letter map from the standings
|
|
2784
|
-
* endpoint. Best-effort: returns {} if standings are unavailable.
|
|
2834
|
+
* endpoint. Best-effort: returns {} if standings are unavailable. Reuses the
|
|
2835
|
+
* same parse as {@link fetchStandings}, so the two never drift.
|
|
2785
2836
|
*/
|
|
2786
2837
|
async fetchGroupMap(force = false) {
|
|
2787
2838
|
if (this.groupMap && !force) return this.groupMap;
|
|
2788
|
-
const base = this.opts.baseUrl ?? DEFAULT_BASE;
|
|
2789
|
-
const standingsUrl = `${base.replace("/apis/site/v2/", "/apis/v2/")}/standings`;
|
|
2790
2839
|
const map = {};
|
|
2791
2840
|
try {
|
|
2792
|
-
const
|
|
2793
|
-
for (const
|
|
2794
|
-
const letter = (child.name ?? child.abbreviation ?? "").match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
|
|
2795
|
-
if (!letter) continue;
|
|
2796
|
-
for (const e of child.standings?.entries ?? []) {
|
|
2797
|
-
const code = e.team?.abbreviation?.toUpperCase();
|
|
2798
|
-
if (code) map[code] = letter;
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2841
|
+
const tables = parseStandings(await this.get(this.standingsUrl()));
|
|
2842
|
+
for (const t of tables) for (const r of t.rows) map[r.team.code] = t.group;
|
|
2801
2843
|
} catch {
|
|
2802
2844
|
}
|
|
2803
2845
|
this.groupMap = map;
|
|
@@ -2868,6 +2910,22 @@ async function getMatchesForDate(adapter, dateISO) {
|
|
|
2868
2910
|
return { matches: base, degraded: true };
|
|
2869
2911
|
}
|
|
2870
2912
|
}
|
|
2913
|
+
async function getStandings(adapter, group) {
|
|
2914
|
+
const want = group?.toUpperCase();
|
|
2915
|
+
if (adapter.fetchStandings) {
|
|
2916
|
+
try {
|
|
2917
|
+
const all = await adapter.fetchStandings();
|
|
2918
|
+
const tables2 = (want ? all.filter((t) => t.group === want) : all).sort(
|
|
2919
|
+
(a, b) => a.group.localeCompare(b.group)
|
|
2920
|
+
);
|
|
2921
|
+
return { tables: tables2, degraded: false, source: adapter.name };
|
|
2922
|
+
} catch {
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
const letters = want ? [want] : groups();
|
|
2926
|
+
const tables = letters.map((g) => ({ group: g, rows: computeStandings(fixturesByGroup(g)) })).filter((t) => t.rows.length > 0);
|
|
2927
|
+
return { tables, degraded: true };
|
|
2928
|
+
}
|
|
2871
2929
|
function shiftUtcDate(dateISO, days) {
|
|
2872
2930
|
const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
|
|
2873
2931
|
return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
|
|
@@ -3407,11 +3465,53 @@ function formatShareSnippet(input, options = {}) {
|
|
|
3407
3465
|
blocks.push(card.join("\n"));
|
|
3408
3466
|
}
|
|
3409
3467
|
}
|
|
3468
|
+
if (input.degraded && input.matches.length > 0) {
|
|
3469
|
+
blocks.push("(Live data unavailable \u2014 showing the bundled schedule, not live scores.)");
|
|
3470
|
+
}
|
|
3471
|
+
blocks.push(
|
|
3472
|
+
shareFooter({
|
|
3473
|
+
source: input.source,
|
|
3474
|
+
installLine: input.installLine,
|
|
3475
|
+
includeHashtag,
|
|
3476
|
+
includeInstall
|
|
3477
|
+
})
|
|
3478
|
+
);
|
|
3479
|
+
return blocks.join("\n\n");
|
|
3480
|
+
}
|
|
3481
|
+
function shareFooter(opts) {
|
|
3410
3482
|
const footer = [];
|
|
3411
|
-
if (
|
|
3412
|
-
footer.push(
|
|
3413
|
-
|
|
3414
|
-
|
|
3483
|
+
if (opts.source) footer.push(`Live data: ${liveSourceLabel(opts.source)}`);
|
|
3484
|
+
footer.push(
|
|
3485
|
+
[opts.includeHashtag ? SHARE_HASHTAG : "", SHARE_DISCLAIMER].filter(Boolean).join(" \xB7 ")
|
|
3486
|
+
);
|
|
3487
|
+
if (opts.includeInstall && opts.installLine) footer.push(`Try it: ${opts.installLine}`);
|
|
3488
|
+
return footer.join("\n");
|
|
3489
|
+
}
|
|
3490
|
+
function gd(n) {
|
|
3491
|
+
return n > 0 ? `+${n}` : `${n}`;
|
|
3492
|
+
}
|
|
3493
|
+
function tableRow(r, rank) {
|
|
3494
|
+
return `${rank}. ${r.team.flag} ${r.team.code} ${r.points} pts \xB7 ${r.won}-${r.drawn}-${r.lost} \xB7 ${gd(r.goalDiff)}`;
|
|
3495
|
+
}
|
|
3496
|
+
function formatShareTable(input, options = {}) {
|
|
3497
|
+
const includeHashtag = options.includeHashtag !== false;
|
|
3498
|
+
const includeInstall = options.includeInstallLine !== false;
|
|
3499
|
+
const blocks = [];
|
|
3500
|
+
if (input.tables.length === 0) {
|
|
3501
|
+
blocks.push(input.emptyNote ?? "No standings available.");
|
|
3502
|
+
} else {
|
|
3503
|
+
for (const { group, rows } of input.tables) {
|
|
3504
|
+
blocks.push(
|
|
3505
|
+
[`Group ${group} \xB7 standings`, "", ...rows.map((r, i) => tableRow(r, i + 1))].join("\n")
|
|
3506
|
+
);
|
|
3507
|
+
}
|
|
3508
|
+
if (input.degraded) {
|
|
3509
|
+
blocks.push("(Live standings unavailable \u2014 group roster, not live results.)");
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
blocks.push(
|
|
3513
|
+
shareFooter({ source: input.source, installLine: input.installLine, includeHashtag, includeInstall })
|
|
3514
|
+
);
|
|
3415
3515
|
return blocks.join("\n\n");
|
|
3416
3516
|
}
|
|
3417
3517
|
|
|
@@ -3468,8 +3568,8 @@ function standingsTable(group, rows) {
|
|
|
3468
3568
|
const cols = "Team P W D L GD Pts";
|
|
3469
3569
|
const lines = rows.map((r) => {
|
|
3470
3570
|
const name = `${r.team.flag} ${r.team.name}`.padEnd(24).slice(0, 24);
|
|
3471
|
-
const
|
|
3472
|
-
return `${name} ${pad(r.played)} ${pad(r.won)} ${pad(r.drawn)} ${pad(r.lost)} ${
|
|
3571
|
+
const gd2 = (r.goalDiff > 0 ? `+${r.goalDiff}` : `${r.goalDiff}`).padStart(3);
|
|
3572
|
+
return `${name} ${pad(r.played)} ${pad(r.won)} ${pad(r.drawn)} ${pad(r.lost)} ${gd2} ${pad(r.points)}`;
|
|
3473
3573
|
});
|
|
3474
3574
|
return [header, cols, ...lines].join("\n");
|
|
3475
3575
|
}
|
|
@@ -3592,8 +3692,9 @@ async function toolGetToday(args) {
|
|
|
3592
3692
|
const { matches, degraded, source } = await getMatchesForDate(adapter, date);
|
|
3593
3693
|
const todays = fixturesByDate(date, matches, args.tz);
|
|
3594
3694
|
const opts = fmtOpts(args);
|
|
3595
|
-
|
|
3695
|
+
let text = `Matches on ${date}:
|
|
3596
3696
|
${matchList(todays, "No matches scheduled.", opts)}`;
|
|
3697
|
+
if (degraded) text += "\n\n(Live scores unavailable \u2014 showing the bundled schedule.)";
|
|
3597
3698
|
const marketSignals = await reliableMarketData(args, todays);
|
|
3598
3699
|
return {
|
|
3599
3700
|
text: withDisclaimer(text, source),
|
|
@@ -3611,7 +3712,7 @@ async function toolGetLive(args = {}) {
|
|
|
3611
3712
|
const adapter = resolveAdapter(args);
|
|
3612
3713
|
const { matches, degraded, source } = await getLiveMatches(adapter);
|
|
3613
3714
|
const opts = fmtOpts(args);
|
|
3614
|
-
const text = `Live now:
|
|
3715
|
+
const text = degraded ? "Live scores unavailable right now \u2014 could not reach the data provider." : `Live now:
|
|
3615
3716
|
${matchList(matches, "No matches in play right now.", opts)}`;
|
|
3616
3717
|
return {
|
|
3617
3718
|
text: withDisclaimer(text, source),
|
|
@@ -3631,8 +3732,9 @@ async function toolGetMatch(args) {
|
|
|
3631
3732
|
if (s && isReliableMarketSignal(s, { now })) marketSignal = s;
|
|
3632
3733
|
}
|
|
3633
3734
|
const base = matchLine(match, opts);
|
|
3634
|
-
|
|
3735
|
+
let text = marketSignal ? `${base}
|
|
3635
3736
|
${marketBlock(marketSignal, match).join("\n")}` : base;
|
|
3737
|
+
if (degraded) text += "\n\n(Live state unavailable \u2014 showing the scheduled fixture.)";
|
|
3636
3738
|
return {
|
|
3637
3739
|
text: withDisclaimer(text, liveSource),
|
|
3638
3740
|
data: {
|
|
@@ -3644,34 +3746,34 @@ ${marketBlock(marketSignal, match).join("\n")}` : base;
|
|
|
3644
3746
|
};
|
|
3645
3747
|
}
|
|
3646
3748
|
async function toolGetStandings(args) {
|
|
3647
|
-
const {
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
text: withDisclaimer(`No group "${g}". Groups are ${known.join(", ")}.`, source),
|
|
3657
|
-
data: { degraded, source: source ?? null, tables: null }
|
|
3658
|
-
};
|
|
3659
|
-
}
|
|
3749
|
+
const { tables, degraded, source } = await getStandings(resolveAdapter(args), args.group);
|
|
3750
|
+
const shaped = tables.map((tb) => ({ group: tb.group, standings: tb.rows }));
|
|
3751
|
+
if (shaped.length === 0) {
|
|
3752
|
+
const g = args.group?.toUpperCase();
|
|
3753
|
+
const msg = g ? `No group "${g}". Groups are A\u2013L.` : "No standings available.";
|
|
3754
|
+
return {
|
|
3755
|
+
text: withDisclaimer(degraded ? `${msg} (Live standings unavailable.)` : msg, source),
|
|
3756
|
+
data: { degraded, source: source ?? null, tables: args.group ? null : [] }
|
|
3757
|
+
};
|
|
3660
3758
|
}
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
group: g,
|
|
3664
|
-
standings: computeStandings(fixturesByGroup(g, matches))
|
|
3665
|
-
}));
|
|
3666
|
-
const text = tables.map((t) => standingsTable(t.group, t.standings)).join("\n\n");
|
|
3759
|
+
let text = shaped.map((t) => standingsTable(t.group, t.standings)).join("\n\n");
|
|
3760
|
+
if (degraded) text += "\n\n(Live standings unavailable \u2014 showing the group roster.)";
|
|
3667
3761
|
return {
|
|
3668
|
-
text: withDisclaimer(text
|
|
3669
|
-
data: { degraded, source: source ?? null, tables: args.group ?
|
|
3762
|
+
text: withDisclaimer(text, source),
|
|
3763
|
+
data: { degraded, source: source ?? null, tables: args.group ? shaped[0] ?? null : shaped }
|
|
3670
3764
|
};
|
|
3671
3765
|
}
|
|
3766
|
+
async function standingsResourceText(group, adapter) {
|
|
3767
|
+
const g = group.toUpperCase();
|
|
3768
|
+
const { tables, degraded, source } = await getStandings(adapter, g);
|
|
3769
|
+
const tb = tables[0];
|
|
3770
|
+
let text = tb ? standingsTable(tb.group, tb.rows) : `No group ${g}.`;
|
|
3771
|
+
if (degraded && tb) text += "\n\n(Live standings unavailable \u2014 showing the group roster.)";
|
|
3772
|
+
return withDisclaimer(text, source);
|
|
3773
|
+
}
|
|
3672
3774
|
async function toolGetNextFixture(args) {
|
|
3673
3775
|
const code = args.team.toUpperCase();
|
|
3674
|
-
const fixture = nextFixtureForTeam(code);
|
|
3776
|
+
const fixture = nextFixtureForTeam(code, { from: args.now ?? /* @__PURE__ */ new Date() });
|
|
3675
3777
|
if (!fixture) {
|
|
3676
3778
|
return {
|
|
3677
3779
|
text: withDisclaimer(`No upcoming fixture found for ${code}.`),
|
|
@@ -3772,6 +3874,7 @@ function shareResult(kind, target, team, input, options) {
|
|
|
3772
3874
|
target,
|
|
3773
3875
|
...team ? { team } : {},
|
|
3774
3876
|
source: input.source ?? null,
|
|
3877
|
+
degraded: input.degraded ?? false,
|
|
3775
3878
|
informationalOnly: true,
|
|
3776
3879
|
style: options.style ?? "social",
|
|
3777
3880
|
snippet,
|
|
@@ -3789,7 +3892,7 @@ async function toolGetShareSnippet(args) {
|
|
|
3789
3892
|
const options = shareOptions(args);
|
|
3790
3893
|
const signalsFor = (ms) => args.includeMarkets === false ? Promise.resolve(/* @__PURE__ */ new Map()) : reliableSignalMap(args, ms);
|
|
3791
3894
|
if (args.live) {
|
|
3792
|
-
const { matches, source: source2 } = await getLiveMatches(resolveAdapter(args));
|
|
3895
|
+
const { matches, degraded: degraded2, source: source2 } = await getLiveMatches(resolveAdapter(args));
|
|
3793
3896
|
return shareResult(
|
|
3794
3897
|
"live",
|
|
3795
3898
|
"live",
|
|
@@ -3798,7 +3901,9 @@ async function toolGetShareSnippet(args) {
|
|
|
3798
3901
|
title: "Live match pulse",
|
|
3799
3902
|
matches,
|
|
3800
3903
|
source: source2,
|
|
3801
|
-
|
|
3904
|
+
degraded: degraded2,
|
|
3905
|
+
// Feed down ⇒ don't let an empty card read as "nothing is on".
|
|
3906
|
+
emptyNote: degraded2 ? "Live scores unavailable right now \u2014 couldn't reach the data provider." : "No matches in play right now.",
|
|
3802
3907
|
installLine: "npx @claudinho/cli live",
|
|
3803
3908
|
tz: args.tz,
|
|
3804
3909
|
locale: args.lang
|
|
@@ -3806,8 +3911,37 @@ async function toolGetShareSnippet(args) {
|
|
|
3806
3911
|
{ ...options, includeMarkets: false }
|
|
3807
3912
|
);
|
|
3808
3913
|
}
|
|
3914
|
+
if (args.group) {
|
|
3915
|
+
const group = args.group.toUpperCase();
|
|
3916
|
+
const { tables, degraded: degraded2, source: source2 } = await getStandings(resolveAdapter(args), group);
|
|
3917
|
+
const snippet = formatShareTable(
|
|
3918
|
+
{
|
|
3919
|
+
tables,
|
|
3920
|
+
// Degraded ⇒ static roster, no live provider: don't attribute one, and
|
|
3921
|
+
// surface the not-live notice (the card gets pasted publicly).
|
|
3922
|
+
source: degraded2 ? void 0 : source2,
|
|
3923
|
+
installLine: `npx @claudinho/cli table ${group}`,
|
|
3924
|
+
emptyNote: `No group ${group}.`,
|
|
3925
|
+
degraded: degraded2
|
|
3926
|
+
},
|
|
3927
|
+
options
|
|
3928
|
+
);
|
|
3929
|
+
return {
|
|
3930
|
+
text: snippet,
|
|
3931
|
+
data: {
|
|
3932
|
+
kind: "table",
|
|
3933
|
+
target: "table",
|
|
3934
|
+
group,
|
|
3935
|
+
source: degraded2 ? null : source2 ?? null,
|
|
3936
|
+
degraded: degraded2,
|
|
3937
|
+
informationalOnly: true,
|
|
3938
|
+
snippet,
|
|
3939
|
+
tables: tables.map((tb) => ({ group: tb.group, standings: tb.rows }))
|
|
3940
|
+
}
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3809
3943
|
if (args.matchId) {
|
|
3810
|
-
const { match, source: source2 } = await getMatchById(resolveAdapter(args), args.matchId);
|
|
3944
|
+
const { match, degraded: degraded2, source: source2 } = await getMatchById(resolveAdapter(args), args.matchId);
|
|
3811
3945
|
const matches = match ? [match] : [];
|
|
3812
3946
|
return shareResult(
|
|
3813
3947
|
"match",
|
|
@@ -3818,6 +3952,7 @@ async function toolGetShareSnippet(args) {
|
|
|
3818
3952
|
matches,
|
|
3819
3953
|
marketSignals: await signalsFor(matches),
|
|
3820
3954
|
source: source2,
|
|
3955
|
+
degraded: degraded2,
|
|
3821
3956
|
emptyNote: `No match found with id ${args.matchId}.`,
|
|
3822
3957
|
installLine: `npx @claudinho/cli match ${args.matchId}`,
|
|
3823
3958
|
tz: args.tz,
|
|
@@ -3828,7 +3963,7 @@ async function toolGetShareSnippet(args) {
|
|
|
3828
3963
|
}
|
|
3829
3964
|
if (args.team) {
|
|
3830
3965
|
const code = args.team.toUpperCase();
|
|
3831
|
-
const fixture = nextFixtureForTeam(code);
|
|
3966
|
+
const fixture = nextFixtureForTeam(code, { from: args.now ?? /* @__PURE__ */ new Date() });
|
|
3832
3967
|
const matches = fixture ? [fixture] : [];
|
|
3833
3968
|
const teamName = fixture ? fixture.home.code === code ? fixture.home.name : fixture.away.name : code;
|
|
3834
3969
|
return shareResult(
|
|
@@ -3848,7 +3983,7 @@ async function toolGetShareSnippet(args) {
|
|
|
3848
3983
|
);
|
|
3849
3984
|
}
|
|
3850
3985
|
const date = args.date ?? localDate((/* @__PURE__ */ new Date()).toISOString(), args.tz);
|
|
3851
|
-
const { matches: all, source } = await getMatchesForDate(resolveAdapter(args), date);
|
|
3986
|
+
const { matches: all, degraded, source } = await getMatchesForDate(resolveAdapter(args), date);
|
|
3852
3987
|
const todays = fixturesByDate(date, all, args.tz);
|
|
3853
3988
|
const human = formatDate(`${date}T12:00:00.000Z`, { tz: args.tz, locale: args.lang });
|
|
3854
3989
|
return shareResult(
|
|
@@ -3860,6 +3995,7 @@ async function toolGetShareSnippet(args) {
|
|
|
3860
3995
|
matches: todays,
|
|
3861
3996
|
marketSignals: await signalsFor(todays),
|
|
3862
3997
|
source,
|
|
3998
|
+
degraded,
|
|
3863
3999
|
emptyNote: `No matches scheduled for ${human}.`,
|
|
3864
4000
|
installLine: "npx @claudinho/cli today",
|
|
3865
4001
|
tz: args.tz,
|
|
@@ -3871,7 +4007,7 @@ async function toolGetShareSnippet(args) {
|
|
|
3871
4007
|
|
|
3872
4008
|
// src/server.ts
|
|
3873
4009
|
var SERVER_NAME = "claudinho";
|
|
3874
|
-
var SERVER_VERSION = "0.
|
|
4010
|
+
var SERVER_VERSION = "0.5.1";
|
|
3875
4011
|
var VOICE = asFlavorLevel(process.env.CLAUDINHO_FLAVOR) === "off" ? "" : `
|
|
3876
4012
|
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.`;
|
|
3877
4013
|
var INSTRUCTIONS = `Claudinho serves live scores, fixtures, and group standings for the 2026 men's football tournament.
|
|
@@ -3905,7 +4041,7 @@ function buildServer() {
|
|
|
3905
4041
|
"get_today",
|
|
3906
4042
|
{
|
|
3907
4043
|
title: "Today's matches",
|
|
3908
|
-
description: "
|
|
4044
|
+
description: "All fixtures for a date (default: today), with live score and minute overlaid on any match in play. Use this for a whole day's card; for only in-play matches use get_live, for one team's match use get_next_fixture, for a single match's detail use get_match. Kickoffs render in tz; lang localizes text (en/es/pt/fr); flavor sets commentary tone.",
|
|
3909
4045
|
inputSchema: {
|
|
3910
4046
|
date: dateArg.optional().describe("Date as YYYY-MM-DD (default: today)"),
|
|
3911
4047
|
...commonArgs
|
|
@@ -3919,7 +4055,7 @@ function buildServer() {
|
|
|
3919
4055
|
"get_live",
|
|
3920
4056
|
{
|
|
3921
4057
|
title: "Live matches",
|
|
3922
|
-
description: "
|
|
4058
|
+
description: "Only matches in play right now \u2014 each with current score and minute (empty when nothing is live). Use during matches for in-play state; for a full day's schedule including upcoming and finished, use get_today. tz/lang/flavor affect formatting only.",
|
|
3923
4059
|
inputSchema: { ...commonArgs },
|
|
3924
4060
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
3925
4061
|
},
|
|
@@ -3929,7 +4065,7 @@ function buildServer() {
|
|
|
3929
4065
|
"get_match",
|
|
3930
4066
|
{
|
|
3931
4067
|
title: "Match detail",
|
|
3932
|
-
description: "
|
|
4068
|
+
description: "One match by its id, with live score/minute overlaid when it's in play. Get the id from get_today or get_live; to find a team's match without an id, use get_next_fixture. tz/lang/flavor affect formatting.",
|
|
3933
4069
|
inputSchema: { id: z.string().describe("Match id"), ...commonArgs },
|
|
3934
4070
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
3935
4071
|
},
|
|
@@ -3939,7 +4075,7 @@ function buildServer() {
|
|
|
3939
4075
|
"get_standings",
|
|
3940
4076
|
{
|
|
3941
4077
|
title: "Group standings",
|
|
3942
|
-
description: "
|
|
4078
|
+
description: "Live cumulative group standings \u2014 pass a group letter A\u2013L, or omit for all 12. Returns ranked rows (team, played, W/D/L, goal difference, points). Use get_today for fixtures/scores and get_next_fixture for one team. Falls back to a roster at zero (flagged degraded) if live standings are unavailable.",
|
|
3943
4079
|
inputSchema: {
|
|
3944
4080
|
group: groupArg.optional().describe("Group letter A\u2013L (omit for all)"),
|
|
3945
4081
|
...commonArgs
|
|
@@ -3981,10 +4117,11 @@ function buildServer() {
|
|
|
3981
4117
|
"get_share_snippet",
|
|
3982
4118
|
{
|
|
3983
4119
|
title: "Shareable match snippet",
|
|
3984
|
-
description:
|
|
4120
|
+
description: `A polished, copy-pasteable card (plain text) for a match (matchId), a team's next fixture (team), a group's standings table (group, e.g. "A"), a date (default: today), or live matches (live: true). Returns the ready-to-paste snippet plus structured data \u2014 hand the snippet text to the user verbatim. No links; it carries a non-affiliation disclaimer, and any market line stays informational only.`,
|
|
3985
4121
|
inputSchema: {
|
|
3986
4122
|
matchId: z.string().optional().describe("Match id (most specific)"),
|
|
3987
4123
|
team: teamArg.optional().describe("3-letter team code for that team's next fixture, e.g. MEX"),
|
|
4124
|
+
group: groupArg.optional().describe("Group letter A\u2013L for a standings card, e.g. A"),
|
|
3988
4125
|
date: dateArg.optional().describe("Date as YYYY-MM-DD (default: today)"),
|
|
3989
4126
|
live: z.boolean().optional().describe("Snapshot of matches in play right now"),
|
|
3990
4127
|
style: z.enum(["social", "compact"]).optional().describe("social (default, full card) or compact (one line per match)"),
|
|
@@ -4003,13 +4140,12 @@ function buildServer() {
|
|
|
4003
4140
|
new ResourceTemplate("standings://{group}", { list: void 0 }),
|
|
4004
4141
|
{
|
|
4005
4142
|
title: "Group standings",
|
|
4006
|
-
description: "
|
|
4143
|
+
description: "Live group table for a group letter A\u2013L.",
|
|
4007
4144
|
mimeType: "text/plain"
|
|
4008
4145
|
},
|
|
4009
4146
|
async (uri, variables) => {
|
|
4010
|
-
const group = String(variables.group ?? "")
|
|
4011
|
-
const
|
|
4012
|
-
const text = rows.length ? standingsTable(group, rows) : `No group ${group}.`;
|
|
4147
|
+
const group = String(variables.group ?? "");
|
|
4148
|
+
const text = await standingsResourceText(group, makeAdapter());
|
|
4013
4149
|
return { contents: [{ uri: uri.href, mimeType: "text/plain", text }] };
|
|
4014
4150
|
}
|
|
4015
4151
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"tsup": "^8.0.0",
|
|
55
55
|
"typescript": "^5.7.0",
|
|
56
56
|
"vitest": "^4.1.0",
|
|
57
|
-
"@claudinho/core": "0.
|
|
57
|
+
"@claudinho/core": "0.5.1"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"build": "tsup",
|