@claudinho/mcp 0.8.14 → 0.8.16

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 +2 -1
  2. package/dist/index.js +114 -29
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -54,7 +54,8 @@ Then just ask your agent naturally — it picks the right tool and answers with
54
54
 
55
55
  All tools are **read-only** (`readOnlyHint`) and accept optional `tz`, `lang`
56
56
  (`en`/`es`/`pt`/`fr`), and `flavor` (`off`/`subtle`/`full`). Every response carries
57
- human-readable text **and** structured JSON.
57
+ human-readable text **and** structured content, validated against each tool's
58
+ declared `outputSchema`.
58
59
 
59
60
  Resources: `standings://{group}`, `fixtures://{date}`. Prompts: `tournament_today`,
60
61
  and `my_team` (give it a 3-letter team code; combines next fixture, standings, and
package/dist/index.js CHANGED
@@ -3116,7 +3116,7 @@ function winnerLabel(ctx, stage, index) {
3116
3116
  n: String(index)
3117
3117
  });
3118
3118
  }
3119
- function resolveSlot(ref, ctx, liveTeam) {
3119
+ function resolveSlot(ref, ctx, liveTeam, fixtureInMergedSet = false) {
3120
3120
  const liveParticipant = confirmedLiveParticipant(liveTeam);
3121
3121
  switch (ref.kind) {
3122
3122
  case "seed":
@@ -3138,23 +3138,27 @@ function resolveSlot(ref, ctx, liveTeam) {
3138
3138
  if (liveParticipant) return liveParticipant;
3139
3139
  return tbd(t(ctx.lang, "bracket.slot.third", { groups: ref.groups.join("/") }));
3140
3140
  case "winner": {
3141
- const node = ctx.nodesByKey.get(matchKey(ref.stage, ref.index));
3142
- const match = node ? ctx.matchesById.get(node.matchId) : void 0;
3143
- if (match) {
3144
- const winner = resolveWinner(match);
3145
- if (winner) return participant(winner, "confirmed");
3146
- }
3147
3141
  if (liveParticipant) return liveParticipant;
3142
+ if (!fixtureInMergedSet) {
3143
+ const node = ctx.nodesByKey.get(matchKey(ref.stage, ref.index));
3144
+ const match = node ? ctx.matchesById.get(node.matchId) : void 0;
3145
+ if (match) {
3146
+ const winner = resolveWinner(match);
3147
+ if (winner) return participant(winner, "confirmed");
3148
+ }
3149
+ }
3148
3150
  return tbd(winnerLabel(ctx, ref.stage, ref.index));
3149
3151
  }
3150
3152
  case "loser": {
3151
- const node = ctx.nodesByKey.get(matchKey(ref.stage, ref.index));
3152
- const match = node ? ctx.matchesById.get(node.matchId) : void 0;
3153
- if (match) {
3154
- const loser = resolveLoser(match);
3155
- if (loser) return participant(loser, "confirmed");
3156
- }
3157
3153
  if (liveParticipant) return liveParticipant;
3154
+ if (!fixtureInMergedSet) {
3155
+ const node = ctx.nodesByKey.get(matchKey(ref.stage, ref.index));
3156
+ const match = node ? ctx.matchesById.get(node.matchId) : void 0;
3157
+ if (match) {
3158
+ const loser = resolveLoser(match);
3159
+ if (loser) return participant(loser, "confirmed");
3160
+ }
3161
+ }
3158
3162
  return tbd(
3159
3163
  t(ctx.lang, "bracket.slot.loser", {
3160
3164
  stage: stageLabelI18n(ctx.lang, ref.stage),
@@ -3177,6 +3181,7 @@ function buildBracketView(topology, matches, tables, standingsDegraded, liveDegr
3177
3181
  if (want && stage !== want) continue;
3178
3182
  const nodes = topology.matches.filter((n) => n.stage === stage);
3179
3183
  const matchViews = nodes.map((node) => {
3184
+ const fixtureInMergedSet = matchesById.has(node.matchId);
3180
3185
  const match = matchesById.get(node.matchId) ?? {
3181
3186
  id: node.matchId,
3182
3187
  stage: node.stage,
@@ -3192,8 +3197,8 @@ function buildBracketView(topology, matches, tables, standingsDegraded, liveDegr
3192
3197
  stage: node.stage,
3193
3198
  index: node.index,
3194
3199
  kickoff: match.kickoff,
3195
- home: resolveSlot(node.home, ctx, match.home),
3196
- away: resolveSlot(node.away, ctx, match.away),
3200
+ home: resolveSlot(node.home, ctx, match.home, fixtureInMergedSet),
3201
+ away: resolveSlot(node.away, ctx, match.away, fixtureInMergedSet),
3197
3202
  match
3198
3203
  };
3199
3204
  });
@@ -4035,8 +4040,8 @@ function marketProbabilityText(signal, match) {
4035
4040
  }
4036
4041
  function marketAttributionText(signal) {
4037
4042
  const time = utcHhmm(signal.asOf);
4038
- const src = `Source: ${marketSourceLabel(signal.source)}`;
4039
- return time ? `${src} \xB7 updated ${time}` : src;
4043
+ const src2 = `Source: ${marketSourceLabel(signal.source)}`;
4044
+ return time ? `${src2} \xB7 updated ${time}` : src2;
4040
4045
  }
4041
4046
  function marketLine(signal, match) {
4042
4047
  return `Market: ${marketProbabilityText(signal, match)} \xB7 ${marketSourceLabel(
@@ -4534,8 +4539,8 @@ function formatShareBracket(input, options = {}) {
4534
4539
  }
4535
4540
  }
4536
4541
  const footer = [];
4537
- const src = input.source ?? input.view.source;
4538
- if (src) footer.push(t(locale, "live.data", { source: liveSourceLabel(src) }));
4542
+ const src2 = input.source ?? input.view.source;
4543
+ if (src2) footer.push(t(locale, "live.data", { source: liveSourceLabel(src2) }));
4539
4544
  footer.push(
4540
4545
  [includeHashtag ? SHARE_HASHTAG : "", SHARE_DISCLAIMER].filter(Boolean).join(" \xB7 ")
4541
4546
  );
@@ -5114,7 +5119,7 @@ async function toolGetShareSnippet(args) {
5114
5119
 
5115
5120
  // src/server.ts
5116
5121
  var SERVER_NAME = "claudinho";
5117
- var SERVER_VERSION = "0.8.14";
5122
+ var SERVER_VERSION = "0.8.16";
5118
5123
  var VOICE = asFlavorLevel(process.env.CLAUDINHO_FLAVOR) === "off" ? "" : `
5119
5124
  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.`;
5120
5125
  var INSTRUCTIONS = `Claudinho serves live scores, fixtures, and group standings for the 2026 men's football tournament.
@@ -5131,12 +5136,84 @@ var commonArgs = {
5131
5136
  lang: z.string().optional().describe("Locale for formatting: en, es, pt, fr (other locales fall back to en)"),
5132
5137
  flavor: flavorArg.optional().describe("Commentary flair: off, subtle, full (default: full)")
5133
5138
  };
5139
+ var teamRef = z.object({ code: z.string(), name: z.string(), flag: z.string() }).partial().passthrough();
5140
+ var scorePair = z.object({ home: z.number(), away: z.number() }).partial().passthrough();
5141
+ var matchOut = z.object({
5142
+ id: z.string(),
5143
+ stage: z.string().optional(),
5144
+ group: z.string().nullable().optional(),
5145
+ kickoff: z.string().optional(),
5146
+ venue: z.string().optional(),
5147
+ home: teamRef.optional(),
5148
+ away: teamRef.optional(),
5149
+ score: scorePair.nullable().optional(),
5150
+ shootout: scorePair.optional(),
5151
+ status: z.string().optional(),
5152
+ minute: z.number().nullable().optional(),
5153
+ winnerCode: z.string().optional()
5154
+ }).passthrough();
5155
+ var anyObj = z.object({}).passthrough();
5156
+ var src = z.string().nullable();
5157
+ var todayOut = {
5158
+ date: z.string(),
5159
+ degraded: z.boolean(),
5160
+ source: src,
5161
+ count: z.number(),
5162
+ matches: z.array(matchOut),
5163
+ marketSignals: z.record(anyObj).optional()
5164
+ };
5165
+ var liveOut = { degraded: z.boolean(), source: src, count: z.number(), matches: z.array(matchOut) };
5166
+ var matchDetailOut = {
5167
+ match: matchOut.nullable(),
5168
+ degraded: z.boolean().optional(),
5169
+ source: src.optional(),
5170
+ marketSignal: anyObj.nullable().optional()
5171
+ };
5172
+ var standingsOut = {
5173
+ degraded: z.boolean(),
5174
+ source: src,
5175
+ tables: z.union([anyObj, z.array(anyObj), z.null()])
5176
+ };
5177
+ var bracketOut = {
5178
+ view: anyObj.nullable(),
5179
+ degraded: z.boolean().optional(),
5180
+ standingsDegraded: z.boolean().optional(),
5181
+ source: src.optional()
5182
+ };
5183
+ var nextOut = { team: z.string(), fixture: matchOut.nullable(), degraded: z.boolean() };
5184
+ var marketOut = {
5185
+ matchId: z.string().nullable().optional(),
5186
+ team: z.string().optional(),
5187
+ date: z.string().optional(),
5188
+ degraded: z.boolean().optional(),
5189
+ informationalOnly: z.boolean(),
5190
+ signal: anyObj.nullable().optional(),
5191
+ signals: z.array(anyObj).optional()
5192
+ };
5193
+ var shareOut = {
5194
+ kind: z.string(),
5195
+ target: z.string().optional(),
5196
+ snippet: z.string().optional(),
5197
+ // absent on the bracket "unknown stage" error branch
5198
+ source: src.optional(),
5199
+ informationalOnly: z.boolean().optional(),
5200
+ degraded: z.boolean().optional(),
5201
+ style: z.string().optional(),
5202
+ team: z.string().optional(),
5203
+ group: z.string().optional(),
5204
+ stage: z.string().optional(),
5205
+ tables: z.union([anyObj, z.array(anyObj), z.null()]).optional(),
5206
+ view: anyObj.nullable().optional(),
5207
+ matches: z.array(matchOut).optional(),
5208
+ marketSignals: z.record(anyObj).optional()
5209
+ };
5134
5210
  function toContent(r) {
5135
5211
  return {
5136
5212
  content: [
5137
5213
  { type: "text", text: r.text },
5138
5214
  { type: "text", text: "```json\n" + JSON.stringify(r.data, null, 2) + "\n```" }
5139
- ]
5215
+ ],
5216
+ structuredContent: r.data
5140
5217
  };
5141
5218
  }
5142
5219
  function buildServer() {
@@ -5154,7 +5231,8 @@ function buildServer() {
5154
5231
  ...commonArgs
5155
5232
  },
5156
5233
  // Read-only; reaches an external data provider for the live overlay.
5157
- annotations: { readOnlyHint: true, openWorldHint: true }
5234
+ annotations: { readOnlyHint: true, openWorldHint: true },
5235
+ outputSchema: todayOut
5158
5236
  },
5159
5237
  async (args) => toContent(await toolGetToday(args))
5160
5238
  );
@@ -5164,7 +5242,8 @@ function buildServer() {
5164
5242
  title: "Live matches",
5165
5243
  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.",
5166
5244
  inputSchema: { ...commonArgs },
5167
- annotations: { readOnlyHint: true, openWorldHint: true }
5245
+ annotations: { readOnlyHint: true, openWorldHint: true },
5246
+ outputSchema: liveOut
5168
5247
  },
5169
5248
  async (args) => toContent(await toolGetLive(args))
5170
5249
  );
@@ -5174,7 +5253,8 @@ function buildServer() {
5174
5253
  title: "Match detail",
5175
5254
  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.",
5176
5255
  inputSchema: { id: z.string().describe("Match id"), ...commonArgs },
5177
- annotations: { readOnlyHint: true, openWorldHint: true }
5256
+ annotations: { readOnlyHint: true, openWorldHint: true },
5257
+ outputSchema: matchDetailOut
5178
5258
  },
5179
5259
  async (args) => toContent(await toolGetMatch(args))
5180
5260
  );
@@ -5187,7 +5267,8 @@ function buildServer() {
5187
5267
  group: groupArg.optional().describe("Group letter A\u2013L (omit for all)"),
5188
5268
  ...commonArgs
5189
5269
  },
5190
- annotations: { readOnlyHint: true, openWorldHint: true }
5270
+ annotations: { readOnlyHint: true, openWorldHint: true },
5271
+ outputSchema: standingsOut
5191
5272
  },
5192
5273
  async (args) => toContent(await toolGetStandings(args))
5193
5274
  );
@@ -5200,7 +5281,8 @@ function buildServer() {
5200
5281
  stage: z.enum(["R32", "R16", "QF", "SF", "3P", "F"]).optional().describe("Knockout round to show (omit for the full bracket)"),
5201
5282
  ...commonArgs
5202
5283
  },
5203
- annotations: { readOnlyHint: true, openWorldHint: true }
5284
+ annotations: { readOnlyHint: true, openWorldHint: true },
5285
+ outputSchema: bracketOut
5204
5286
  },
5205
5287
  async (args) => toContent(await toolGetBracket(args))
5206
5288
  );
@@ -5211,7 +5293,8 @@ function buildServer() {
5211
5293
  description: "A team's next match, live-resolved: a confirmed knockout tie (Round of 32 onward) is read from the live overlay, group fixtures from the bundled schedule. Use a 3-letter code, e.g. MEX, BRA, USA. Falls back to the bundled schedule if the provider is unreachable.",
5212
5294
  inputSchema: { team: teamArg.describe("3-letter team code, e.g. MEX"), ...commonArgs },
5213
5295
  // Read-only; overlays live provider data for knockout pairings, so open-world.
5214
- annotations: { readOnlyHint: true, openWorldHint: true }
5296
+ annotations: { readOnlyHint: true, openWorldHint: true },
5297
+ outputSchema: nextOut
5215
5298
  },
5216
5299
  async (args) => toContent(await toolGetNextFixture(args))
5217
5300
  );
@@ -5229,7 +5312,8 @@ function buildServer() {
5229
5312
  ...commonArgs
5230
5313
  },
5231
5314
  // Read-only; reaches an external prediction-market data provider.
5232
- annotations: { readOnlyHint: true, openWorldHint: true }
5315
+ annotations: { readOnlyHint: true, openWorldHint: true },
5316
+ outputSchema: marketOut
5233
5317
  },
5234
5318
  async (args) => toContent(await toolGetMarketSignal(args))
5235
5319
  );
@@ -5253,7 +5337,8 @@ function buildServer() {
5253
5337
  ...commonArgs
5254
5338
  },
5255
5339
  // Read-only; may reach the live/market data providers (live/today/match).
5256
- annotations: { readOnlyHint: true, openWorldHint: true }
5340
+ annotations: { readOnlyHint: true, openWorldHint: true },
5341
+ outputSchema: shareOut
5257
5342
  },
5258
5343
  async (args) => toContent(await toolGetShareSnippet(args))
5259
5344
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudinho/mcp",
3
- "version": "0.8.14",
3
+ "version": "0.8.16",
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",
@@ -57,10 +57,11 @@
57
57
  "tsup": "^8.0.0",
58
58
  "typescript": "^5.7.0",
59
59
  "vitest": "^4.1.9",
60
- "@claudinho/core": "0.8.14"
60
+ "@claudinho/core": "0.8.16"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "tsup",
64
+ "build:mcpb": "pnpm run build && node scripts/build-mcpb.mjs",
64
65
  "dev": "tsup --watch",
65
66
  "test": "vitest run",
66
67
  "typecheck": "tsc --noEmit",