@claudinho/mcp 0.11.0 → 0.11.2
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 +5 -4
- package/dist/index.js +102 -60
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**An MCP server with live football scores, fixtures, standings and paste-ready match cards for
|
|
4
4
|
the competition you follow: the World Cup, the Premier League, LALIGA, the Champions League and
|
|
5
|
-
|
|
5
|
+
12 more.** Ask your agent in Claude Code, Cursor, Codex, Claude Desktop, Windsurf, Zed, VS Code,
|
|
6
6
|
or any MCP client (stdio). No API key, no signup. The World Cup's 104 fixtures ship bundled; the
|
|
7
7
|
other competitions' schedules are read from the feed, and live scores (and the optional,
|
|
8
8
|
read-only Polymarket signals for the World Cup) hit the network. It ran the 2026 World Cup from
|
|
@@ -86,9 +86,10 @@ match's `stage` is the written one (`REGULAR` for a league's season, `LEAGUE` fo
|
|
|
86
86
|
league phase, `PO` for play-offs), or `OTHER` with the provider's own words in `stageLabel`.
|
|
87
87
|
|
|
88
88
|
Resources: `standings://{group}` (its text starts with the competition it is for, like a tool's), `fixtures://{date}` (the bundled World Cup schedule, named first, whatever the competition). Prompts: `tournament_today`,
|
|
89
|
-
and `my_team` (the
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
and `my_team` (the competition you follow: give it a club's or a nation's code or name, or
|
|
90
|
+
none for your team (the server's `CLAUDINHO_TEAM`, else the one pinned with `claudinho follow --team`);
|
|
91
|
+
it asks for the next fixture with its date and state and the team's standing, and on the World Cup
|
|
92
|
+
for the prediction-market read of that fixture).
|
|
92
93
|
|
|
93
94
|
## Market signals
|
|
94
95
|
|
package/dist/index.js
CHANGED
|
@@ -769,7 +769,7 @@ function formatTime(iso, opts = {}) {
|
|
|
769
769
|
}).format(when);
|
|
770
770
|
}
|
|
771
771
|
var COUNTDOWN_NOW = "now";
|
|
772
|
-
function countdown(iso, from
|
|
772
|
+
function countdown(iso, from) {
|
|
773
773
|
const when = parsedDate(iso);
|
|
774
774
|
if (!when) return UNKNOWN_TIME;
|
|
775
775
|
const ms = when.getTime() - from.getTime();
|
|
@@ -782,7 +782,7 @@ function countdown(iso, from = /* @__PURE__ */ new Date()) {
|
|
|
782
782
|
if (hours > 0) return `${hours}h${mins}m`;
|
|
783
783
|
return `${mins}m`;
|
|
784
784
|
}
|
|
785
|
-
function countdownPhrase(lang, iso, from
|
|
785
|
+
function countdownPhrase(lang, iso, from) {
|
|
786
786
|
const left = countdown(iso, from);
|
|
787
787
|
return left === COUNTDOWN_NOW ? t(lang, "countdown.now") : t(lang, "next.in", { countdown: left });
|
|
788
788
|
}
|
|
@@ -4074,6 +4074,7 @@ var SUPPORTED = Object.freeze(
|
|
|
4074
4074
|
{ slug: "ita.1", alias: "serie-a", name: "Serie A", teams: "club", kind: "league", seasonSlug: "italian-serie-a", standings: "league", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
|
|
4075
4075
|
{ slug: "ger.1", alias: "bundesliga", name: "Bundesliga", teams: "club", kind: "league", seasonSlug: "german-bundesliga", standings: "league", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
|
|
4076
4076
|
{ slug: "mex.1", alias: "liga-mx", name: "Liga MX", teams: "club", kind: "league", standings: "league", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
|
|
4077
|
+
{ slug: "uru.1", alias: "liga-auf", name: "Liga AUF Uruguaya", teams: "club", kind: "league", standings: "league", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
|
|
4077
4078
|
{ slug: "uefa.champions", alias: "champions-league", name: "Champions League", teams: "club", kind: "cup", standings: "league", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
|
|
4078
4079
|
{ slug: "conmebol.libertadores", alias: "libertadores", name: "Libertadores", teams: "club", kind: "cup", standings: "groups", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
|
|
4079
4080
|
{ slug: "concacaf.champions", alias: "concacaf-champions-cup", name: "Concacaf Champions Cup", teams: "club", kind: "cup", standings: "none", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
|
|
@@ -4727,7 +4728,7 @@ function fetchMeta(result) {
|
|
|
4727
4728
|
}
|
|
4728
4729
|
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
4729
4730
|
var DEFAULT_COMPETITION = BUNDLED_SLUG;
|
|
4730
|
-
var USER_AGENT = `claudinho/${"0.11.
|
|
4731
|
+
var USER_AGENT = `claudinho/${"0.11.2"} (+https://github.com/arturogarrido/claudinho)`;
|
|
4731
4732
|
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
4732
4733
|
var DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
4733
4734
|
var MAX_COOLDOWN_MS = 15 * 6e4;
|
|
@@ -6216,6 +6217,9 @@ function mergeLive(base, live) {
|
|
|
6216
6217
|
function clockOf(adapter) {
|
|
6217
6218
|
return new Date(adapter.now?.() ?? Date.now());
|
|
6218
6219
|
}
|
|
6220
|
+
function requestClock(adapter, given) {
|
|
6221
|
+
return given ?? clockOf(adapter);
|
|
6222
|
+
}
|
|
6219
6223
|
function providerDayOf(adapter, instant) {
|
|
6220
6224
|
if (Number.isNaN(instant.getTime())) return "";
|
|
6221
6225
|
return adapter.bucketDay?.(instant) || instant.toISOString().slice(0, 10);
|
|
@@ -6390,7 +6394,7 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
6390
6394
|
});
|
|
6391
6395
|
let refreshRead = {};
|
|
6392
6396
|
if (candidate) {
|
|
6393
|
-
const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id);
|
|
6397
|
+
const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id, now);
|
|
6394
6398
|
refreshRead = partial ? { partial } : {};
|
|
6395
6399
|
const m = r.degraded ? candidate : r.match ?? candidate;
|
|
6396
6400
|
if (!isFinished(m.status)) return { ...r, match: m, ...bothReads(windowRead, refreshRead) };
|
|
@@ -8078,13 +8082,13 @@ var STATUS_KEY = {
|
|
|
8078
8082
|
function flairOptsOf(opts) {
|
|
8079
8083
|
return { level: opts.flavor, locale: opts.locale, kind: opts.teamKind, pin: opts.pin };
|
|
8080
8084
|
}
|
|
8081
|
-
function matchLine(m, opts
|
|
8085
|
+
function matchLine(m, opts, flair = matchFlair(m, flairOptsOf(opts))) {
|
|
8082
8086
|
const head = `${withFlag(m.home.name, m.home.flag, "home")} ${scoreline(m)} ${withFlag(m.away.name, m.away.flag, "away")}`;
|
|
8083
8087
|
const lang = opts.locale;
|
|
8084
8088
|
const stage = stageLabelI18n(lang, m);
|
|
8085
8089
|
let tail;
|
|
8086
8090
|
if (m.status === "SCHEDULED") {
|
|
8087
|
-
tail = `${formatKickoff(m.kickoff, opts)} (${countdownPhrase(lang, m.kickoff)})`;
|
|
8091
|
+
tail = `${formatKickoff(m.kickoff, opts)} (${countdownPhrase(lang, m.kickoff, opts.now)})`;
|
|
8088
8092
|
} else if (m.status === "LIVE") {
|
|
8089
8093
|
tail = m.minute ? `${t(lang, "status.live")} ${m.minute}'` : t(lang, "status.live");
|
|
8090
8094
|
} else {
|
|
@@ -8103,7 +8107,7 @@ function truncationNote(list) {
|
|
|
8103
8107
|
function headingLine(lang, title) {
|
|
8104
8108
|
return t(lang, "heading", { title });
|
|
8105
8109
|
}
|
|
8106
|
-
function matchRows(matches, empty, opts
|
|
8110
|
+
function matchRows(matches, empty, opts) {
|
|
8107
8111
|
if (matches.length === 0) return empty;
|
|
8108
8112
|
const shown = matches.slice(0, MAX_LIST_MATCHES);
|
|
8109
8113
|
const flairs = matchFlairs(shown, flairOptsOf(opts));
|
|
@@ -8113,7 +8117,7 @@ function listTruncation(matches) {
|
|
|
8113
8117
|
const overflow = matches.length - MAX_LIST_MATCHES;
|
|
8114
8118
|
return overflow > 0 ? `(list truncated \u2014 ${overflow} more not shown)` : void 0;
|
|
8115
8119
|
}
|
|
8116
|
-
function matchList(matches, empty, opts
|
|
8120
|
+
function matchList(matches, empty, opts) {
|
|
8117
8121
|
const rows = matchRows(matches, empty, opts);
|
|
8118
8122
|
const truncated = listTruncation(matches);
|
|
8119
8123
|
return truncated ? `${rows}
|
|
@@ -8149,7 +8153,7 @@ function capSignals(signals, kept) {
|
|
|
8149
8153
|
// src/tools.ts
|
|
8150
8154
|
import { homedir } from "os";
|
|
8151
8155
|
var adapters = /* @__PURE__ */ new Map();
|
|
8152
|
-
var KEPT_ADAPTERS_MAX =
|
|
8156
|
+
var KEPT_ADAPTERS_MAX = 2 * SUPPORTED.length;
|
|
8153
8157
|
var sourceWindows = /* @__PURE__ */ new Map();
|
|
8154
8158
|
function throttleable(adapter) {
|
|
8155
8159
|
const t2 = adapter;
|
|
@@ -8369,9 +8373,8 @@ async function cachedMarketSignals(args, matches, providerFactory = makeMarketPr
|
|
|
8369
8373
|
}
|
|
8370
8374
|
return { signals: result, complete };
|
|
8371
8375
|
}
|
|
8372
|
-
async function reliableMarketData(args, matches) {
|
|
8376
|
+
async function reliableMarketData(args, matches, now) {
|
|
8373
8377
|
if (!marketsEnabled()) return { data: void 0, complete: true };
|
|
8374
|
-
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
8375
8378
|
const relevant = matches.filter((m) => marketRelevant(m, now));
|
|
8376
8379
|
if (relevant.length === 0) return { data: void 0, complete: true };
|
|
8377
8380
|
const result = await cachedMarketSignals(args, relevant);
|
|
@@ -8387,9 +8390,11 @@ async function reliableMarketData(args, matches) {
|
|
|
8387
8390
|
complete: result.complete
|
|
8388
8391
|
};
|
|
8389
8392
|
}
|
|
8390
|
-
function fmtOpts(args, competition) {
|
|
8393
|
+
function fmtOpts(args, competition, now) {
|
|
8391
8394
|
const pin = choiceOf(args).pin;
|
|
8392
8395
|
return {
|
|
8396
|
+
// The tool's clock, read once where the tool reads it: a line's countdown is relative to it.
|
|
8397
|
+
now,
|
|
8393
8398
|
tz: args.tz,
|
|
8394
8399
|
locale: args.lang,
|
|
8395
8400
|
flavor: asFlavorLevel(args.flavor ?? process.env.CLAUDINHO_FLAVOR),
|
|
@@ -8430,17 +8435,18 @@ function toolGetToday(args) {
|
|
|
8430
8435
|
}
|
|
8431
8436
|
async function todayAnswer(args) {
|
|
8432
8437
|
const adapter = resolveAdapter(args);
|
|
8433
|
-
const
|
|
8438
|
+
const now = requestClock(adapter, args.now);
|
|
8439
|
+
const date = args.date ?? localDate(now.toISOString(), args.tz);
|
|
8434
8440
|
const day = await getMatchesForDate(adapter, date, resolveTz(args.tz));
|
|
8435
8441
|
const { matches, degraded, source } = day;
|
|
8436
8442
|
const todays = fixturesByDate(date, matches, args.tz);
|
|
8437
8443
|
const shownToday = boundedRecords(todays);
|
|
8438
|
-
const opts = fmtOpts(args, adapter.competition);
|
|
8444
|
+
const opts = fmtOpts(args, adapter.competition, now);
|
|
8439
8445
|
const empty = verdictNotice(day, args.lang) ?? dateUnreached(day, date, args.lang) ?? dateNoneRead(day, date, args.lang) ?? t(args.lang, "today.none");
|
|
8440
8446
|
const text = `${headingLine(args.lang, t(args.lang, "today.onDate", { date }))}
|
|
8441
8447
|
${matchRows(todays, empty, opts)}`;
|
|
8442
8448
|
const degradedLine = degraded ? `(${t(args.lang, day.skeleton ? "feed.degraded" : "live.degraded")})` : void 0;
|
|
8443
|
-
const market = await reliableMarketData(args, todays);
|
|
8449
|
+
const market = await reliableMarketData(args, todays, now);
|
|
8444
8450
|
const marketLine2 = market.complete ? void 0 : "(Market data unavailable or incomplete \u2014 not all fixtures were checked.)";
|
|
8445
8451
|
const attribution = dayAttribution(day, shownToday.items, args.lang);
|
|
8446
8452
|
return {
|
|
@@ -8478,9 +8484,10 @@ function toolGetLive(args = {}) {
|
|
|
8478
8484
|
}
|
|
8479
8485
|
async function liveAnswer(args) {
|
|
8480
8486
|
const adapter = resolveAdapter(args);
|
|
8481
|
-
const
|
|
8487
|
+
const now = requestClock(adapter, args.now);
|
|
8488
|
+
const live = await getLiveMatches(adapter, now);
|
|
8482
8489
|
const { matches, degraded, source } = live;
|
|
8483
|
-
const opts = fmtOpts(args, adapter.competition);
|
|
8490
|
+
const opts = fmtOpts(args, adapter.competition, now);
|
|
8484
8491
|
const text = degraded ? t(args.lang, "live.degraded") : `${headingLine(args.lang, t(args.lang, "live.title"))}
|
|
8485
8492
|
${matchRows(matches, verdictNotice(live, args.lang) ?? liveNoneRead(live, args.lang) ?? t(args.lang, "live.none"), opts)}`;
|
|
8486
8493
|
const shownLive = boundedRecords(matches);
|
|
@@ -8503,7 +8510,8 @@ function toolGetMatch(args) {
|
|
|
8503
8510
|
}
|
|
8504
8511
|
async function matchAnswer(args) {
|
|
8505
8512
|
const adapter = resolveAdapter(args);
|
|
8506
|
-
const
|
|
8513
|
+
const now = requestClock(adapter, args.now);
|
|
8514
|
+
const found = await getMatchById(adapter, args.id, now);
|
|
8507
8515
|
const { match, degraded, source: liveSource } = found;
|
|
8508
8516
|
if (!match) {
|
|
8509
8517
|
const msg = verdictNotice(found, args.lang) ?? matchWindowSentence(found, args.id, args.lang) ?? matchNoneReadSentence(found, args.id, args.lang) ?? t(args.lang, degraded ? "match.unreachable" : "match.none", { id: args.id });
|
|
@@ -8519,8 +8527,7 @@ async function matchAnswer(args) {
|
|
|
8519
8527
|
}
|
|
8520
8528
|
};
|
|
8521
8529
|
}
|
|
8522
|
-
const opts = fmtOpts(args, adapter.competition);
|
|
8523
|
-
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
8530
|
+
const opts = fmtOpts(args, adapter.competition, now);
|
|
8524
8531
|
let marketSignal;
|
|
8525
8532
|
let marketComplete = true;
|
|
8526
8533
|
if (marketsEnabled() && marketRelevant(match, now)) {
|
|
@@ -8695,7 +8702,7 @@ async function nextAnswer(args) {
|
|
|
8695
8702
|
if (!query) throw new Error(NO_TEAM);
|
|
8696
8703
|
const asked = pin ? { code: query } : bundleApplies(adapter.competition) ? nationArg(query) : { code: query };
|
|
8697
8704
|
const code = "code" in asked ? asked.code : query;
|
|
8698
|
-
const now = args.now
|
|
8705
|
+
const now = requestClock(adapter, args.now);
|
|
8699
8706
|
const next = pin ? await nextFixtureForPin(adapter, pin, now) : "code" in asked ? await getNextFixtureForTeam(adapter, code, now) : asked.answer;
|
|
8700
8707
|
const { fixture, degraded, source } = next;
|
|
8701
8708
|
const label = nextTeamLabel(next, code);
|
|
@@ -8715,7 +8722,7 @@ ${msg}`, next, args.lang), void 0, args.lang),
|
|
|
8715
8722
|
data: { ...about, fixture: null, degraded, source: source ?? null, ...verdictExtras(next) }
|
|
8716
8723
|
};
|
|
8717
8724
|
}
|
|
8718
|
-
const opts = fmtOpts(args, adapter.competition);
|
|
8725
|
+
const opts = fmtOpts(args, adapter.competition, now);
|
|
8719
8726
|
return {
|
|
8720
8727
|
// `source` in data mirrors the text's "Live data: …" attribution (parity
|
|
8721
8728
|
// with CLI `next --json`); null for a static group fixture (no live source).
|
|
@@ -8759,7 +8766,6 @@ function toolGetMarketSignal(args) {
|
|
|
8759
8766
|
}
|
|
8760
8767
|
async function marketAnswer(args) {
|
|
8761
8768
|
const provider = resolveMarketProvider(args);
|
|
8762
|
-
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
8763
8769
|
if (args.matchId) {
|
|
8764
8770
|
const scope = marketScopeVerdict(competitionOf(args), 0);
|
|
8765
8771
|
const outOfScope = verdictNotice(scope, args.lang);
|
|
@@ -8775,13 +8781,15 @@ async function marketAnswer(args) {
|
|
|
8775
8781
|
}
|
|
8776
8782
|
};
|
|
8777
8783
|
}
|
|
8778
|
-
const
|
|
8784
|
+
const adapter2 = resolveAdapter(args);
|
|
8785
|
+
const now2 = requestClock(adapter2, args.now);
|
|
8786
|
+
const found = await getMatchById(adapter2, args.matchId, now2);
|
|
8779
8787
|
const { match } = found;
|
|
8780
|
-
const relevant = match ? marketRelevant(match,
|
|
8788
|
+
const relevant = match ? marketRelevant(match, now2) : false;
|
|
8781
8789
|
const batch2 = match && relevant ? await getMarketSignals(provider, [match], MARKETS_TOOL_OPTS) : { results: /* @__PURE__ */ new Map(), complete: true };
|
|
8782
8790
|
const sig = match ? resolvedValues(batch2).get(match.id) : void 0;
|
|
8783
8791
|
const shown2 = batch2.complete && match && sig && marketDisplayable(match, sig) ? sig : void 0;
|
|
8784
|
-
const text2 = !match ? verdictNotice(found, args.lang) ?? `No match found with id ${args.matchId}.` : !batch2.complete ? `Market data unavailable or incomplete for ${marketHeader(match, args)} \u2014 this match could not be checked.` : shown2 ? marketText(match, shown2, args) : noSignalText(match, args,
|
|
8792
|
+
const text2 = !match ? verdictNotice(found, args.lang) ?? `No match found with id ${args.matchId}.` : !batch2.complete ? `Market data unavailable or incomplete for ${marketHeader(match, args)} \u2014 this match could not be checked.` : shown2 ? marketText(match, shown2, args) : noSignalText(match, args, now2);
|
|
8785
8793
|
return {
|
|
8786
8794
|
// The fixture read was not whole: said first, apart from `complete`
|
|
8787
8795
|
// (the market requests' own completeness).
|
|
@@ -8797,13 +8805,15 @@ async function marketAnswer(args) {
|
|
|
8797
8805
|
}
|
|
8798
8806
|
if (args.team) {
|
|
8799
8807
|
const code = args.team.toUpperCase();
|
|
8800
|
-
const
|
|
8808
|
+
const adapter2 = resolveAdapter(args);
|
|
8809
|
+
const now2 = requestClock(adapter2, args.now);
|
|
8810
|
+
const picked = await marketFixtureForTeam(adapter2, code, now2);
|
|
8801
8811
|
const { match: fixture, degraded } = picked;
|
|
8802
|
-
const relevant = fixture ? marketRelevant(fixture,
|
|
8812
|
+
const relevant = fixture ? marketRelevant(fixture, now2) : false;
|
|
8803
8813
|
const batch2 = fixture && relevant ? await getMarketSignals(provider, [fixture], MARKETS_TOOL_OPTS) : { results: /* @__PURE__ */ new Map(), complete: true };
|
|
8804
8814
|
const sig = fixture ? resolvedValues(batch2).get(fixture.id) : void 0;
|
|
8805
8815
|
const shown2 = batch2.complete && fixture && sig && marketDisplayable(fixture, sig) ? sig : void 0;
|
|
8806
|
-
const text2 = !fixture ? verdictNotice(picked, args.lang) ?? (degraded ? `Live feed unavailable \u2014 can't resolve ${code}'s next fixture right now.` : `No upcoming fixture found for ${code}.`) : !batch2.complete ? `Market data unavailable or incomplete for ${marketHeader(fixture, args)} \u2014 this match could not be checked.` : shown2 ? marketText(fixture, shown2, args) : noSignalText(fixture, args,
|
|
8816
|
+
const text2 = !fixture ? verdictNotice(picked, args.lang) ?? (degraded ? `Live feed unavailable \u2014 can't resolve ${code}'s next fixture right now.` : `No upcoming fixture found for ${code}.`) : !batch2.complete ? `Market data unavailable or incomplete for ${marketHeader(fixture, args)} \u2014 this match could not be checked.` : shown2 ? marketText(fixture, shown2, args) : noSignalText(fixture, args, now2);
|
|
8807
8817
|
return {
|
|
8808
8818
|
// Either read the pick was made from was not whole: said first, apart
|
|
8809
8819
|
// from `complete` (the market requests' own completeness).
|
|
@@ -8819,8 +8829,10 @@ async function marketAnswer(args) {
|
|
|
8819
8829
|
}
|
|
8820
8830
|
};
|
|
8821
8831
|
}
|
|
8832
|
+
const adapter = resolveAdapter(args);
|
|
8833
|
+
const now = requestClock(adapter, args.now);
|
|
8822
8834
|
const date = args.date ?? localDate(now.toISOString(), args.tz);
|
|
8823
|
-
const day = await getMatchesForDate(
|
|
8835
|
+
const day = await getMatchesForDate(adapter, date, resolveTz(args.tz));
|
|
8824
8836
|
const fixtureRead = marketsCoverCompetition(competitionOf(args)) ? day : {};
|
|
8825
8837
|
const todays = fixturesByDate(date, day.matches, args.tz).filter((m) => marketRelevant(m, now));
|
|
8826
8838
|
const batch = await getMarketSignals(provider, todays, MARKETS_TOOL_OPTS);
|
|
@@ -8862,9 +8874,8 @@ ${shown.items.map(({ match, signal }) => marketText(match, signal, args)).join("
|
|
|
8862
8874
|
}
|
|
8863
8875
|
};
|
|
8864
8876
|
}
|
|
8865
|
-
async function reliableSignalMap(args, matches) {
|
|
8877
|
+
async function reliableSignalMap(args, matches, now) {
|
|
8866
8878
|
if (!marketsEnabled()) return { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
8867
|
-
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
8868
8879
|
const relevant = matches.filter((m) => marketRelevant(m, now));
|
|
8869
8880
|
if (relevant.length === 0) return { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
8870
8881
|
const result = await cachedMarketSignals(args, relevant);
|
|
@@ -8937,11 +8948,12 @@ function shareKindAsked(args) {
|
|
|
8937
8948
|
}
|
|
8938
8949
|
async function shareAnswer(args) {
|
|
8939
8950
|
const options = shareOptions(args);
|
|
8940
|
-
const signalsFor = (ms) => args.includeMarkets === false ? Promise.resolve({ signals: /* @__PURE__ */ new Map(), complete: true }) : reliableSignalMap(args, ms);
|
|
8951
|
+
const signalsFor = (ms, now2) => args.includeMarkets === false ? Promise.resolve({ signals: /* @__PURE__ */ new Map(), complete: true }) : reliableSignalMap(args, ms, now2);
|
|
8941
8952
|
const competition = selectedOf(args).slug;
|
|
8942
8953
|
const where = { tz: args.tz, locale: args.lang, competition };
|
|
8943
8954
|
if (args.live) {
|
|
8944
|
-
const
|
|
8955
|
+
const adapter2 = resolveAdapter(args);
|
|
8956
|
+
const live = await getLiveMatches(adapter2, requestClock(adapter2, args.now));
|
|
8945
8957
|
const shownLive = boundedRecords(live.matches);
|
|
8946
8958
|
return shareResult(
|
|
8947
8959
|
liveShareCard(live, where, {
|
|
@@ -9011,24 +9023,29 @@ async function shareAnswer(args) {
|
|
|
9011
9023
|
};
|
|
9012
9024
|
}
|
|
9013
9025
|
if (args.matchId) {
|
|
9014
|
-
const
|
|
9015
|
-
const
|
|
9026
|
+
const adapter2 = resolveAdapter(args);
|
|
9027
|
+
const now2 = requestClock(adapter2, args.now);
|
|
9028
|
+
const found = await getMatchById(adapter2, args.matchId, now2);
|
|
9029
|
+
const market2 = await signalsFor(found.match ? [found.match] : [], now2);
|
|
9016
9030
|
return shareResult(matchShareCard(found, args.matchId, market2, where), options);
|
|
9017
9031
|
}
|
|
9018
9032
|
if (args.team) {
|
|
9019
|
-
const
|
|
9033
|
+
const adapter2 = resolveAdapter(args);
|
|
9020
9034
|
const query = humanLabel(args.team, 40);
|
|
9021
|
-
const asked = bundleApplies(
|
|
9035
|
+
const asked = bundleApplies(adapter2.competition) ? nationArg(query) : { code: query };
|
|
9022
9036
|
const code = "code" in asked ? asked.code : query;
|
|
9023
|
-
const
|
|
9024
|
-
const
|
|
9037
|
+
const now2 = requestClock(adapter2, args.now);
|
|
9038
|
+
const next = "code" in asked ? await getNextFixtureForTeam(adapter2, code, now2) : asked.answer;
|
|
9039
|
+
const market2 = await signalsFor(next.fixture ? [next.fixture] : [], now2);
|
|
9025
9040
|
return shareResult(nextShareCard(next, code, market2, where), options);
|
|
9026
9041
|
}
|
|
9027
|
-
const
|
|
9028
|
-
const
|
|
9042
|
+
const adapter = resolveAdapter(args);
|
|
9043
|
+
const now = requestClock(adapter, args.now);
|
|
9044
|
+
const date = args.date ?? localDate(now.toISOString(), args.tz);
|
|
9045
|
+
const day = await getMatchesForDate(adapter, date, resolveTz(args.tz));
|
|
9029
9046
|
const todays = fixturesByDate(date, day.matches, args.tz);
|
|
9030
9047
|
const shownToday = boundedRecords(todays);
|
|
9031
|
-
const market = await signalsFor(shownToday.items);
|
|
9048
|
+
const market = await signalsFor(shownToday.items, now);
|
|
9032
9049
|
return shareResult(
|
|
9033
9050
|
dateShareCard(
|
|
9034
9051
|
{
|
|
@@ -9054,7 +9071,7 @@ async function shareAnswer(args) {
|
|
|
9054
9071
|
|
|
9055
9072
|
// src/server.ts
|
|
9056
9073
|
var SERVER_NAME = "claudinho";
|
|
9057
|
-
var SERVER_VERSION = "0.11.
|
|
9074
|
+
var SERVER_VERSION = "0.11.2";
|
|
9058
9075
|
var VOICE = asFlavorLevel(process.env.CLAUDINHO_FLAVOR) === "off" ? "" : `
|
|
9059
9076
|
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!"): use it as a tone cue. Keep every fact exact; never invent details and never impersonate or name a real commentator.`;
|
|
9060
9077
|
var INSTRUCTIONS = `Claudinho serves live scores, fixtures, and standings for one football competition per request, chosen by the tool call's competition argument (an alias such as premier-league, or an ESPN slug such as eng.1), else the server's CLAUDINHO_COMPETITION, else the user's saved choice (set with claudinho follow <alias>). With none of the three, every competition-answering tool answers noCompetition with a sentence saying what to do: pass competition, or ask the user to run claudinho follow <alias>. list_competitions lists the supported competitions, their aliases and what each offers, offline. Every tool's text but get_team's, get_share_snippet's and list_competitions' starts with the competition it is for, and its structured data carries it as competition (get_share_snippet's card names it in its title; list_competitions says it last, as Current, and in data.current; get_team is the World Cup's roster). An unknown competition is a tool error that lists the aliases.
|
|
@@ -9673,8 +9690,9 @@ function buildServer() {
|
|
|
9673
9690
|
},
|
|
9674
9691
|
async (uri, variables) => {
|
|
9675
9692
|
const date = String(variables.date ?? "");
|
|
9693
|
+
const now = /* @__PURE__ */ new Date();
|
|
9676
9694
|
const text = `${competitionLabel(BUNDLE_COMPETITION)}
|
|
9677
|
-
${matchList(fixturesByDate(date, void 0, "UTC"), `No matches on ${date}
|
|
9695
|
+
${matchList(fixturesByDate(date, void 0, "UTC"), `No matches on ${date}.`, { now })}`;
|
|
9678
9696
|
return { contents: [{ uri: uri.href, mimeType: "text/plain", text }] };
|
|
9679
9697
|
}
|
|
9680
9698
|
);
|
|
@@ -9696,28 +9714,52 @@ ${matchList(fixturesByDate(date, void 0, "UTC"), `No matches on ${date}.`)}`;
|
|
|
9696
9714
|
]
|
|
9697
9715
|
})
|
|
9698
9716
|
);
|
|
9699
|
-
|
|
9717
|
+
const myTeamArgs = {
|
|
9718
|
+
team: clubArg.optional().describe(
|
|
9719
|
+
"A club's or a nation's code or name in the competition you follow, e.g. Arsenal, ARS, Mexico or MEX. Omit it for your team: the server's CLAUDINHO_TEAM when it is set, else the one pinned with claudinho follow --team"
|
|
9720
|
+
)
|
|
9721
|
+
};
|
|
9722
|
+
const myTeam = server.registerPrompt(
|
|
9700
9723
|
"my_team",
|
|
9701
9724
|
{
|
|
9702
9725
|
title: "My team",
|
|
9703
|
-
description: "Focus on one
|
|
9704
|
-
argsSchema:
|
|
9726
|
+
description: "Focus on one team's next match in the competition you follow: its date and state, the team's standing in the table, and, on the World Cup, the prediction-market read.",
|
|
9727
|
+
argsSchema: myTeamArgs
|
|
9705
9728
|
},
|
|
9706
9729
|
({ team: asked }) => {
|
|
9707
|
-
const team = humanLabel(asked, 40);
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9730
|
+
const team = asked === void 0 ? "" : humanLabel(asked, 40);
|
|
9731
|
+
const text = [
|
|
9732
|
+
team ? `Tell me about ${team}'s next match and ${team}'s current standing.` : "Tell me about my team's next match and its current standing.",
|
|
9733
|
+
"Call each tool for the competition I follow: pass no competition argument (the server's competition applies, else my saved choice).",
|
|
9734
|
+
"If a tool answers noCompetition, tell me to run claudinho follow <alias> and stop.",
|
|
9735
|
+
"If a tool answers an error naming the competitions, relay it and stop.",
|
|
9736
|
+
...team ? [
|
|
9737
|
+
`Call get_next_fixture with ${team} as team (a team's code or name).`,
|
|
9738
|
+
"If it answers unknownTeam, relay that answer as it is, then ask me which team and stop."
|
|
9739
|
+
] : [
|
|
9740
|
+
"Call get_next_fixture with no team: it answers for my team, the server's CLAUDINHO_TEAM when it is set, else the team I pinned for this competition with claudinho follow --team.",
|
|
9741
|
+
"It answers an error when no team was given, the server's CLAUDINHO_TEAM is unset and none is pinned for this competition, or when CLAUDINHO_TEAM holds nothing readable: then ask me which team and stop.",
|
|
9742
|
+
"If it answers unknownTeam, relay that answer as it is, then ask me which team and stop."
|
|
9743
|
+
],
|
|
9744
|
+
"Always state the fixture's date and its state (scheduled, in play or finished), and call it in play only when the state says so: a next fixture is not a match happening now.",
|
|
9745
|
+
"If get_next_fixture answers no fixture, relay that answer as it is, with its horizon or its verdict when it carries one, and never invent a fixture.",
|
|
9746
|
+
"Name the competition the answer names.",
|
|
9747
|
+
`For the standing, call get_standings and read ${team ? `${team}'s` : "that team's"} row of its table (its group, or the league table).`,
|
|
9748
|
+
"When the competition has no table, or the answer says tables could not be read, say so.",
|
|
9749
|
+
"Only when the competition is the World Cup and get_next_fixture returned a fixture, also call get_market_signal with matchId set to that fixture's id (fixture.id in its data), never with the team's code, and relay what prediction markets say about that match.",
|
|
9750
|
+
"In any other competition, do not call get_market_signal and say nothing about markets.",
|
|
9751
|
+
"When you relay market percentages, treat them as informational context only: relay them factually, never as betting or trading advice."
|
|
9752
|
+
].join(" ");
|
|
9753
|
+
return { messages: [{ role: "user", content: { type: "text", text } }] };
|
|
9719
9754
|
}
|
|
9720
9755
|
);
|
|
9756
|
+
const blankTeamIsNone = (v) => {
|
|
9757
|
+
if (v === void 0 || v === null) return {};
|
|
9758
|
+
if (typeof v !== "object") return v;
|
|
9759
|
+
const { team, ...rest } = v;
|
|
9760
|
+
return typeof team === "string" && /^ *$/.test(team) ? rest : v;
|
|
9761
|
+
};
|
|
9762
|
+
myTeam.argsSchema = Object.assign(z.preprocess(blankTeamIsNone, z.object(myTeamArgs)), { shape: myTeamArgs });
|
|
9721
9763
|
return server;
|
|
9722
9764
|
}
|
|
9723
9765
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/mcp",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"mcpName": "io.github.arturogarrido/claudinho",
|
|
5
|
-
"description": "An MCP server with live football scores, fixtures, standings and paste-ready match cards for the competition you follow: the World Cup, the Premier League, LALIGA, the Champions League and
|
|
5
|
+
"description": "An MCP server with live football scores, fixtures, standings and paste-ready match cards for the competition you follow: the World Cup, the Premier League, LALIGA, the Champions League and 12 more. Works with Claude Code, Cursor, Codex, Claude Desktop, Windsurf, Zed and any MCP client. No API key. Independent fan project. Not affiliated with FIFA, any confederation, league or club, or Anthropic.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "Arturo Garrido",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"serie-a",
|
|
58
58
|
"bundesliga",
|
|
59
59
|
"liga-mx",
|
|
60
|
+
"liga-auf",
|
|
60
61
|
"champions-league",
|
|
61
62
|
"libertadores",
|
|
62
63
|
"concacaf-champions-cup",
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
"tsup": "^8.0.0",
|
|
73
74
|
"typescript": "^5.7.0",
|
|
74
75
|
"vitest": "^5.0.3",
|
|
75
|
-
"@claudinho/core": "0.11.
|
|
76
|
+
"@claudinho/core": "0.11.2"
|
|
76
77
|
},
|
|
77
78
|
"scripts": {
|
|
78
79
|
"build": "tsup",
|