@claudinho/mcp 0.4.3 → 0.5.0
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 +177 -51
- 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,50 @@ function formatShareSnippet(input, options = {}) {
|
|
|
3407
3465
|
blocks.push(card.join("\n"));
|
|
3408
3466
|
}
|
|
3409
3467
|
}
|
|
3468
|
+
blocks.push(
|
|
3469
|
+
shareFooter({
|
|
3470
|
+
source: input.source,
|
|
3471
|
+
installLine: input.installLine,
|
|
3472
|
+
includeHashtag,
|
|
3473
|
+
includeInstall
|
|
3474
|
+
})
|
|
3475
|
+
);
|
|
3476
|
+
return blocks.join("\n\n");
|
|
3477
|
+
}
|
|
3478
|
+
function shareFooter(opts) {
|
|
3410
3479
|
const footer = [];
|
|
3411
|
-
if (
|
|
3412
|
-
footer.push(
|
|
3413
|
-
|
|
3414
|
-
|
|
3480
|
+
if (opts.source) footer.push(`Live data: ${liveSourceLabel(opts.source)}`);
|
|
3481
|
+
footer.push(
|
|
3482
|
+
[opts.includeHashtag ? SHARE_HASHTAG : "", SHARE_DISCLAIMER].filter(Boolean).join(" \xB7 ")
|
|
3483
|
+
);
|
|
3484
|
+
if (opts.includeInstall && opts.installLine) footer.push(`Try it: ${opts.installLine}`);
|
|
3485
|
+
return footer.join("\n");
|
|
3486
|
+
}
|
|
3487
|
+
function gd(n) {
|
|
3488
|
+
return n > 0 ? `+${n}` : `${n}`;
|
|
3489
|
+
}
|
|
3490
|
+
function tableRow(r, rank) {
|
|
3491
|
+
return `${rank}. ${r.team.flag} ${r.team.code} ${r.points} pts \xB7 ${r.won}-${r.drawn}-${r.lost} \xB7 ${gd(r.goalDiff)}`;
|
|
3492
|
+
}
|
|
3493
|
+
function formatShareTable(input, options = {}) {
|
|
3494
|
+
const includeHashtag = options.includeHashtag !== false;
|
|
3495
|
+
const includeInstall = options.includeInstallLine !== false;
|
|
3496
|
+
const blocks = [];
|
|
3497
|
+
if (input.tables.length === 0) {
|
|
3498
|
+
blocks.push(input.emptyNote ?? "No standings available.");
|
|
3499
|
+
} else {
|
|
3500
|
+
for (const { group, rows } of input.tables) {
|
|
3501
|
+
blocks.push(
|
|
3502
|
+
[`Group ${group} \xB7 standings`, "", ...rows.map((r, i) => tableRow(r, i + 1))].join("\n")
|
|
3503
|
+
);
|
|
3504
|
+
}
|
|
3505
|
+
if (input.degraded) {
|
|
3506
|
+
blocks.push("(Live standings unavailable \u2014 group roster, not live results.)");
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
blocks.push(
|
|
3510
|
+
shareFooter({ source: input.source, installLine: input.installLine, includeHashtag, includeInstall })
|
|
3511
|
+
);
|
|
3415
3512
|
return blocks.join("\n\n");
|
|
3416
3513
|
}
|
|
3417
3514
|
|
|
@@ -3468,8 +3565,8 @@ function standingsTable(group, rows) {
|
|
|
3468
3565
|
const cols = "Team P W D L GD Pts";
|
|
3469
3566
|
const lines = rows.map((r) => {
|
|
3470
3567
|
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)} ${
|
|
3568
|
+
const gd2 = (r.goalDiff > 0 ? `+${r.goalDiff}` : `${r.goalDiff}`).padStart(3);
|
|
3569
|
+
return `${name} ${pad(r.played)} ${pad(r.won)} ${pad(r.drawn)} ${pad(r.lost)} ${gd2} ${pad(r.points)}`;
|
|
3473
3570
|
});
|
|
3474
3571
|
return [header, cols, ...lines].join("\n");
|
|
3475
3572
|
}
|
|
@@ -3644,34 +3741,34 @@ ${marketBlock(marketSignal, match).join("\n")}` : base;
|
|
|
3644
3741
|
};
|
|
3645
3742
|
}
|
|
3646
3743
|
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
|
-
}
|
|
3744
|
+
const { tables, degraded, source } = await getStandings(resolveAdapter(args), args.group);
|
|
3745
|
+
const shaped = tables.map((tb) => ({ group: tb.group, standings: tb.rows }));
|
|
3746
|
+
if (shaped.length === 0) {
|
|
3747
|
+
const g = args.group?.toUpperCase();
|
|
3748
|
+
const msg = g ? `No group "${g}". Groups are A\u2013L.` : "No standings available.";
|
|
3749
|
+
return {
|
|
3750
|
+
text: withDisclaimer(degraded ? `${msg} (Live standings unavailable.)` : msg, source),
|
|
3751
|
+
data: { degraded, source: source ?? null, tables: args.group ? null : [] }
|
|
3752
|
+
};
|
|
3660
3753
|
}
|
|
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");
|
|
3754
|
+
let text = shaped.map((t) => standingsTable(t.group, t.standings)).join("\n\n");
|
|
3755
|
+
if (degraded) text += "\n\n(Live standings unavailable \u2014 showing the group roster.)";
|
|
3667
3756
|
return {
|
|
3668
|
-
text: withDisclaimer(text
|
|
3669
|
-
data: { degraded, source: source ?? null, tables: args.group ?
|
|
3757
|
+
text: withDisclaimer(text, source),
|
|
3758
|
+
data: { degraded, source: source ?? null, tables: args.group ? shaped[0] ?? null : shaped }
|
|
3670
3759
|
};
|
|
3671
3760
|
}
|
|
3761
|
+
async function standingsResourceText(group, adapter) {
|
|
3762
|
+
const g = group.toUpperCase();
|
|
3763
|
+
const { tables, degraded, source } = await getStandings(adapter, g);
|
|
3764
|
+
const tb = tables[0];
|
|
3765
|
+
let text = tb ? standingsTable(tb.group, tb.rows) : `No group ${g}.`;
|
|
3766
|
+
if (degraded && tb) text += "\n\n(Live standings unavailable \u2014 showing the group roster.)";
|
|
3767
|
+
return withDisclaimer(text, source);
|
|
3768
|
+
}
|
|
3672
3769
|
async function toolGetNextFixture(args) {
|
|
3673
3770
|
const code = args.team.toUpperCase();
|
|
3674
|
-
const fixture = nextFixtureForTeam(code);
|
|
3771
|
+
const fixture = nextFixtureForTeam(code, { from: args.now ?? /* @__PURE__ */ new Date() });
|
|
3675
3772
|
if (!fixture) {
|
|
3676
3773
|
return {
|
|
3677
3774
|
text: withDisclaimer(`No upcoming fixture found for ${code}.`),
|
|
@@ -3806,6 +3903,35 @@ async function toolGetShareSnippet(args) {
|
|
|
3806
3903
|
{ ...options, includeMarkets: false }
|
|
3807
3904
|
);
|
|
3808
3905
|
}
|
|
3906
|
+
if (args.group) {
|
|
3907
|
+
const group = args.group.toUpperCase();
|
|
3908
|
+
const { tables, degraded, source: source2 } = await getStandings(resolveAdapter(args), group);
|
|
3909
|
+
const snippet = formatShareTable(
|
|
3910
|
+
{
|
|
3911
|
+
tables,
|
|
3912
|
+
// Degraded ⇒ static roster, no live provider: don't attribute one, and
|
|
3913
|
+
// surface the not-live notice (the card gets pasted publicly).
|
|
3914
|
+
source: degraded ? void 0 : source2,
|
|
3915
|
+
installLine: `npx @claudinho/cli table ${group}`,
|
|
3916
|
+
emptyNote: `No group ${group}.`,
|
|
3917
|
+
degraded
|
|
3918
|
+
},
|
|
3919
|
+
options
|
|
3920
|
+
);
|
|
3921
|
+
return {
|
|
3922
|
+
text: snippet,
|
|
3923
|
+
data: {
|
|
3924
|
+
kind: "table",
|
|
3925
|
+
target: "table",
|
|
3926
|
+
group,
|
|
3927
|
+
source: degraded ? null : source2 ?? null,
|
|
3928
|
+
degraded,
|
|
3929
|
+
informationalOnly: true,
|
|
3930
|
+
snippet,
|
|
3931
|
+
tables: tables.map((tb) => ({ group: tb.group, standings: tb.rows }))
|
|
3932
|
+
}
|
|
3933
|
+
};
|
|
3934
|
+
}
|
|
3809
3935
|
if (args.matchId) {
|
|
3810
3936
|
const { match, source: source2 } = await getMatchById(resolveAdapter(args), args.matchId);
|
|
3811
3937
|
const matches = match ? [match] : [];
|
|
@@ -3828,7 +3954,7 @@ async function toolGetShareSnippet(args) {
|
|
|
3828
3954
|
}
|
|
3829
3955
|
if (args.team) {
|
|
3830
3956
|
const code = args.team.toUpperCase();
|
|
3831
|
-
const fixture = nextFixtureForTeam(code);
|
|
3957
|
+
const fixture = nextFixtureForTeam(code, { from: args.now ?? /* @__PURE__ */ new Date() });
|
|
3832
3958
|
const matches = fixture ? [fixture] : [];
|
|
3833
3959
|
const teamName = fixture ? fixture.home.code === code ? fixture.home.name : fixture.away.name : code;
|
|
3834
3960
|
return shareResult(
|
|
@@ -3871,7 +3997,7 @@ async function toolGetShareSnippet(args) {
|
|
|
3871
3997
|
|
|
3872
3998
|
// src/server.ts
|
|
3873
3999
|
var SERVER_NAME = "claudinho";
|
|
3874
|
-
var SERVER_VERSION = "0.
|
|
4000
|
+
var SERVER_VERSION = "0.5.0";
|
|
3875
4001
|
var VOICE = asFlavorLevel(process.env.CLAUDINHO_FLAVOR) === "off" ? "" : `
|
|
3876
4002
|
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
4003
|
var INSTRUCTIONS = `Claudinho serves live scores, fixtures, and group standings for the 2026 men's football tournament.
|
|
@@ -3905,7 +4031,7 @@ function buildServer() {
|
|
|
3905
4031
|
"get_today",
|
|
3906
4032
|
{
|
|
3907
4033
|
title: "Today's matches",
|
|
3908
|
-
description: "
|
|
4034
|
+
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
4035
|
inputSchema: {
|
|
3910
4036
|
date: dateArg.optional().describe("Date as YYYY-MM-DD (default: today)"),
|
|
3911
4037
|
...commonArgs
|
|
@@ -3919,7 +4045,7 @@ function buildServer() {
|
|
|
3919
4045
|
"get_live",
|
|
3920
4046
|
{
|
|
3921
4047
|
title: "Live matches",
|
|
3922
|
-
description: "
|
|
4048
|
+
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
4049
|
inputSchema: { ...commonArgs },
|
|
3924
4050
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
3925
4051
|
},
|
|
@@ -3929,7 +4055,7 @@ function buildServer() {
|
|
|
3929
4055
|
"get_match",
|
|
3930
4056
|
{
|
|
3931
4057
|
title: "Match detail",
|
|
3932
|
-
description: "
|
|
4058
|
+
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
4059
|
inputSchema: { id: z.string().describe("Match id"), ...commonArgs },
|
|
3934
4060
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
3935
4061
|
},
|
|
@@ -3939,7 +4065,7 @@ function buildServer() {
|
|
|
3939
4065
|
"get_standings",
|
|
3940
4066
|
{
|
|
3941
4067
|
title: "Group standings",
|
|
3942
|
-
description: "
|
|
4068
|
+
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
4069
|
inputSchema: {
|
|
3944
4070
|
group: groupArg.optional().describe("Group letter A\u2013L (omit for all)"),
|
|
3945
4071
|
...commonArgs
|
|
@@ -3981,10 +4107,11 @@ function buildServer() {
|
|
|
3981
4107
|
"get_share_snippet",
|
|
3982
4108
|
{
|
|
3983
4109
|
title: "Shareable match snippet",
|
|
3984
|
-
description:
|
|
4110
|
+
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
4111
|
inputSchema: {
|
|
3986
4112
|
matchId: z.string().optional().describe("Match id (most specific)"),
|
|
3987
4113
|
team: teamArg.optional().describe("3-letter team code for that team's next fixture, e.g. MEX"),
|
|
4114
|
+
group: groupArg.optional().describe("Group letter A\u2013L for a standings card, e.g. A"),
|
|
3988
4115
|
date: dateArg.optional().describe("Date as YYYY-MM-DD (default: today)"),
|
|
3989
4116
|
live: z.boolean().optional().describe("Snapshot of matches in play right now"),
|
|
3990
4117
|
style: z.enum(["social", "compact"]).optional().describe("social (default, full card) or compact (one line per match)"),
|
|
@@ -4003,13 +4130,12 @@ function buildServer() {
|
|
|
4003
4130
|
new ResourceTemplate("standings://{group}", { list: void 0 }),
|
|
4004
4131
|
{
|
|
4005
4132
|
title: "Group standings",
|
|
4006
|
-
description: "
|
|
4133
|
+
description: "Live group table for a group letter A\u2013L.",
|
|
4007
4134
|
mimeType: "text/plain"
|
|
4008
4135
|
},
|
|
4009
4136
|
async (uri, variables) => {
|
|
4010
|
-
const group = String(variables.group ?? "")
|
|
4011
|
-
const
|
|
4012
|
-
const text = rows.length ? standingsTable(group, rows) : `No group ${group}.`;
|
|
4137
|
+
const group = String(variables.group ?? "");
|
|
4138
|
+
const text = await standingsResourceText(group, makeAdapter());
|
|
4013
4139
|
return { contents: [{ uri: uri.href, mimeType: "text/plain", text }] };
|
|
4014
4140
|
}
|
|
4015
4141
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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.0"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"build": "tsup",
|