@claudinho/mcp 0.8.18 → 0.8.19
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/dist/index.js +82 -21
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -504,6 +504,33 @@ function shiftUtcDate(dateISO, days) {
|
|
|
504
504
|
const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
|
|
505
505
|
return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
|
|
506
506
|
}
|
|
507
|
+
var FEED_TEXT_MAX = 100;
|
|
508
|
+
function sanitizeFeedText(value, max = FEED_TEXT_MAX) {
|
|
509
|
+
let out = "";
|
|
510
|
+
let count = 0;
|
|
511
|
+
for (const ch of String(value)) {
|
|
512
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
513
|
+
const isWhitespaceControl = cp === 9 || cp === 10 || cp === 13;
|
|
514
|
+
if ((cp <= 31 || cp >= 127 && cp <= 159) && !isWhitespaceControl) continue;
|
|
515
|
+
if (count >= max) break;
|
|
516
|
+
out += isWhitespaceControl ? " " : ch;
|
|
517
|
+
count++;
|
|
518
|
+
}
|
|
519
|
+
return out;
|
|
520
|
+
}
|
|
521
|
+
var segmenter = new Intl.Segmenter();
|
|
522
|
+
var WIDE_CLUSTER = new RegExp("^(?:\\p{Regional_Indicator}|\\p{Extended_Pictographic})", "u");
|
|
523
|
+
function displayWidth(s) {
|
|
524
|
+
let w = 0;
|
|
525
|
+
for (const { segment } of segmenter.segment(s)) {
|
|
526
|
+
w += WIDE_CLUSTER.test(segment) ? 2 : 1;
|
|
527
|
+
}
|
|
528
|
+
return w;
|
|
529
|
+
}
|
|
530
|
+
function padVisible(s, width) {
|
|
531
|
+
const w = displayWidth(s);
|
|
532
|
+
return w >= width ? s : s + " ".repeat(width - w);
|
|
533
|
+
}
|
|
507
534
|
function outcomeFromScore(home, away) {
|
|
508
535
|
if (home > away) return "H";
|
|
509
536
|
if (home < away) return "A";
|
|
@@ -2881,6 +2908,7 @@ var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
|
2881
2908
|
var DEFAULT_COMPETITION = "fifa.world";
|
|
2882
2909
|
var DEFAULT_BASE = `${ESPN_SOCCER}/${DEFAULT_COMPETITION}`;
|
|
2883
2910
|
var USER_AGENT = "claudinho/0.0 (+https://github.com/arturogarrido/claudinho)";
|
|
2911
|
+
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
2884
2912
|
function competitionBase(slug) {
|
|
2885
2913
|
return `${ESPN_SOCCER}/${slug}`;
|
|
2886
2914
|
}
|
|
@@ -2924,9 +2952,15 @@ function toInt(s) {
|
|
|
2924
2952
|
return Number.isFinite(n) ? n : void 0;
|
|
2925
2953
|
}
|
|
2926
2954
|
function toTeam(t2) {
|
|
2927
|
-
const name =
|
|
2928
|
-
|
|
2929
|
-
|
|
2955
|
+
const name = sanitizeFeedText(
|
|
2956
|
+
t2?.displayName ?? t2?.name ?? t2?.location ?? t2?.shortDisplayName ?? "TBD"
|
|
2957
|
+
);
|
|
2958
|
+
const code = sanitizeFeedText(t2?.abbreviation ?? name.slice(0, 3)).toUpperCase();
|
|
2959
|
+
return {
|
|
2960
|
+
code,
|
|
2961
|
+
name,
|
|
2962
|
+
flag: nationToFlag(sanitizeFeedText(t2?.displayName ?? t2?.abbreviation ?? name))
|
|
2963
|
+
};
|
|
2930
2964
|
}
|
|
2931
2965
|
function mapEspnEvent(ev, ctx = {}) {
|
|
2932
2966
|
const comp = ev.competitions?.[0];
|
|
@@ -2957,9 +2991,9 @@ function mapEspnEvent(ev, ctx = {}) {
|
|
|
2957
2991
|
stage,
|
|
2958
2992
|
group,
|
|
2959
2993
|
kickoff: ev.date,
|
|
2960
|
-
venue: comp?.venue?.fullName ?? "",
|
|
2961
|
-
city: comp?.venue?.address?.city || void 0,
|
|
2962
|
-
country: comp?.venue?.address?.country || void 0,
|
|
2994
|
+
venue: sanitizeFeedText(comp?.venue?.fullName ?? ""),
|
|
2995
|
+
city: sanitizeFeedText(comp?.venue?.address?.city ?? "") || void 0,
|
|
2996
|
+
country: sanitizeFeedText(comp?.venue?.address?.country ?? "") || void 0,
|
|
2963
2997
|
home,
|
|
2964
2998
|
away,
|
|
2965
2999
|
score: hasScore ? { home: hs, away: as } : void 0,
|
|
@@ -3083,11 +3117,18 @@ var EspnAdapter = class {
|
|
|
3083
3117
|
try {
|
|
3084
3118
|
const res = await doFetch(url, {
|
|
3085
3119
|
signal: controller.signal,
|
|
3120
|
+
// ESPN never legitimately redirects; following one would sidestep the
|
|
3121
|
+
// fixed-host guarantee, so treat any redirect as a failure (degraded).
|
|
3122
|
+
redirect: "error",
|
|
3086
3123
|
headers: { Accept: "application/json", "User-Agent": USER_AGENT }
|
|
3087
3124
|
});
|
|
3088
3125
|
if (!res.ok) {
|
|
3089
3126
|
throw new Error(`ESPN request failed: ${res.status} ${res.statusText}`);
|
|
3090
3127
|
}
|
|
3128
|
+
const length = Number(res.headers?.get?.("content-length"));
|
|
3129
|
+
if (Number.isFinite(length) && length > MAX_RESPONSE_BYTES) {
|
|
3130
|
+
throw new Error(`ESPN response too large: ${length} bytes`);
|
|
3131
|
+
}
|
|
3091
3132
|
return await res.json();
|
|
3092
3133
|
} finally {
|
|
3093
3134
|
clearTimeout(timer);
|
|
@@ -4229,12 +4270,19 @@ var PolymarketProvider = class {
|
|
|
4229
4270
|
const doFetch = this.opts.fetchImpl ?? fetch;
|
|
4230
4271
|
const res = await doFetch(url, {
|
|
4231
4272
|
signal: AbortSignal.timeout(timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
4273
|
+
// Gamma never legitimately redirects; following one would sidestep the
|
|
4274
|
+
// host allow-list (it only validates the base URL), so reject redirects.
|
|
4275
|
+
redirect: "error",
|
|
4232
4276
|
headers: { Accept: "application/json", "User-Agent": USER_AGENT2 }
|
|
4233
4277
|
});
|
|
4234
4278
|
if (res.status === 404) return void 0;
|
|
4235
4279
|
if (!res.ok) {
|
|
4236
4280
|
throw new Error(`Polymarket request failed: ${res.status} ${res.statusText}`);
|
|
4237
4281
|
}
|
|
4282
|
+
const length = Number(res.headers?.get?.("content-length"));
|
|
4283
|
+
if (Number.isFinite(length) && length > MAX_RESPONSE_BYTES) {
|
|
4284
|
+
throw new Error(`Polymarket response too large: ${length} bytes`);
|
|
4285
|
+
}
|
|
4238
4286
|
const data = await res.json();
|
|
4239
4287
|
const event = Array.isArray(data) ? data[0] : data;
|
|
4240
4288
|
return event && typeof event === "object" ? event : void 0;
|
|
@@ -4677,17 +4725,21 @@ function matchList(matches, empty, opts = {}) {
|
|
|
4677
4725
|
}
|
|
4678
4726
|
function standingsTable(group, rows) {
|
|
4679
4727
|
const header = `Group ${group}`;
|
|
4680
|
-
const
|
|
4681
|
-
const
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4728
|
+
const line = (team, p, w, d, l, gd2, pts) => `${padVisible(team, 24)} ${p.padStart(2)} ${w.padStart(2)} ${d.padStart(2)} ${l.padStart(2)} ${gd2.padStart(3)} ${pts.padStart(3)}`;
|
|
4729
|
+
const cols = line("Team", "P", "W", "D", "L", "GD", "Pts");
|
|
4730
|
+
const lines = rows.map(
|
|
4731
|
+
(r) => line(
|
|
4732
|
+
`${r.team.flag} ${r.team.name}`,
|
|
4733
|
+
String(r.played),
|
|
4734
|
+
String(r.won),
|
|
4735
|
+
String(r.drawn),
|
|
4736
|
+
String(r.lost),
|
|
4737
|
+
r.goalDiff > 0 ? `+${r.goalDiff}` : String(r.goalDiff),
|
|
4738
|
+
String(r.points)
|
|
4739
|
+
)
|
|
4740
|
+
);
|
|
4686
4741
|
return [header, cols, ...lines].join("\n");
|
|
4687
4742
|
}
|
|
4688
|
-
function pad(n) {
|
|
4689
|
-
return `${n}`.padStart(2);
|
|
4690
|
-
}
|
|
4691
4743
|
var DISCLAIMER = "Claudinho is an independent fan project \u2014 not affiliated with or endorsed by FIFA or Anthropic.";
|
|
4692
4744
|
|
|
4693
4745
|
// src/tools.ts
|
|
@@ -4928,14 +4980,16 @@ async function toolGetNextFixture(args) {
|
|
|
4928
4980
|
const msg = degraded ? `Couldn't reach the data provider \u2014 no upcoming fixture confirmed for ${code}.` : `No upcoming fixture found for ${code}.`;
|
|
4929
4981
|
return {
|
|
4930
4982
|
text: withDisclaimer(msg, void 0, args.lang),
|
|
4931
|
-
data: { team: code, fixture: null, degraded }
|
|
4983
|
+
data: { team: code, fixture: null, degraded, source: source ?? null }
|
|
4932
4984
|
};
|
|
4933
4985
|
}
|
|
4934
4986
|
const opts = fmtOpts(args);
|
|
4935
4987
|
return {
|
|
4988
|
+
// `source` in data mirrors the text's "Live data: …" attribution (parity
|
|
4989
|
+
// with CLI `next --json`); null for a static group fixture (no live source).
|
|
4936
4990
|
text: withDisclaimer(`Next up for ${code}:
|
|
4937
4991
|
${matchLine(fixture, opts)}`, source, args.lang),
|
|
4938
|
-
data: { team: code, fixture, degraded }
|
|
4992
|
+
data: { team: code, fixture, degraded, source: source ?? null }
|
|
4939
4993
|
};
|
|
4940
4994
|
}
|
|
4941
4995
|
function toolGetTeam(args) {
|
|
@@ -5219,7 +5273,7 @@ async function toolGetShareSnippet(args) {
|
|
|
5219
5273
|
|
|
5220
5274
|
// src/server.ts
|
|
5221
5275
|
var SERVER_NAME = "claudinho";
|
|
5222
|
-
var SERVER_VERSION = "0.8.
|
|
5276
|
+
var SERVER_VERSION = "0.8.19";
|
|
5223
5277
|
var VOICE = asFlavorLevel(process.env.CLAUDINHO_FLAVOR) === "off" ? "" : `
|
|
5224
5278
|
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.`;
|
|
5225
5279
|
var INSTRUCTIONS = `Claudinho serves live scores, fixtures, and group standings for the 2026 men's football tournament.
|
|
@@ -5234,7 +5288,9 @@ var teamArg = z.string().regex(/^[A-Za-z]{3}$/, "a 3-letter team code, e.g. MEX"
|
|
|
5234
5288
|
var flavorArg = z.enum(["off", "subtle", "full"]);
|
|
5235
5289
|
var commonArgs = {
|
|
5236
5290
|
tz: z.string().optional().describe("IANA timezone for kickoff times, e.g. America/Mexico_City"),
|
|
5237
|
-
lang: z.string().optional().describe(
|
|
5291
|
+
lang: z.string().optional().describe(
|
|
5292
|
+
"Locale for dates, provider attribution, and commentary: en, es, pt, fr (the summary scaffold stays English; other locales fall back to en)"
|
|
5293
|
+
),
|
|
5238
5294
|
flavor: flavorArg.optional().describe("Commentary flair: off, subtle, full (default: full)")
|
|
5239
5295
|
};
|
|
5240
5296
|
var teamRef = z.object({ code: z.string(), name: z.string(), flag: z.string() }).partial().passthrough();
|
|
@@ -5281,7 +5337,12 @@ var bracketOut = {
|
|
|
5281
5337
|
standingsDegraded: z.boolean().optional(),
|
|
5282
5338
|
source: src.optional()
|
|
5283
5339
|
};
|
|
5284
|
-
var nextOut = {
|
|
5340
|
+
var nextOut = {
|
|
5341
|
+
team: z.string(),
|
|
5342
|
+
fixture: matchOut.nullable(),
|
|
5343
|
+
degraded: z.boolean(),
|
|
5344
|
+
source: src
|
|
5345
|
+
};
|
|
5285
5346
|
var marketOut = {
|
|
5286
5347
|
matchId: z.string().nullable().optional(),
|
|
5287
5348
|
team: z.string().optional(),
|
|
@@ -5333,7 +5394,7 @@ function buildServer() {
|
|
|
5333
5394
|
"get_today",
|
|
5334
5395
|
{
|
|
5335
5396
|
title: "Today's matches",
|
|
5336
|
-
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
|
|
5397
|
+
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 dates, attribution, and commentary (en/es/pt/fr); flavor sets commentary tone.",
|
|
5337
5398
|
inputSchema: {
|
|
5338
5399
|
date: dateArg.optional().describe("Date as YYYY-MM-DD (default: today)"),
|
|
5339
5400
|
...commonArgs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.19",
|
|
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",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"dist",
|
|
29
|
+
"!dist/*.mcpb",
|
|
29
30
|
"README.md",
|
|
30
31
|
"LICENSE"
|
|
31
32
|
],
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
"tsup": "^8.0.0",
|
|
58
59
|
"typescript": "^5.7.0",
|
|
59
60
|
"vitest": "^4.1.9",
|
|
60
|
-
"@claudinho/core": "0.8.
|
|
61
|
+
"@claudinho/core": "0.8.19"
|
|
61
62
|
},
|
|
62
63
|
"scripts": {
|
|
63
64
|
"build": "tsup",
|