@davincibets/mcp-server 1.0.1 → 1.3.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 +38 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +1572 -133
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Da Vinci Bets MCP Server
|
|
3
|
+
* Da Vinci Bets MCP Server v1.3.1
|
|
4
4
|
*
|
|
5
5
|
* Gives any AI assistant (Claude, Cursor, Windsurf, etc.) native access
|
|
6
6
|
* to Da Vinci Bets predictions via the user's personal API key.
|
|
7
7
|
*
|
|
8
8
|
* Tools:
|
|
9
|
-
* get_predictions — Fetch AI predictions by sport, market, confidence
|
|
9
|
+
* get_predictions — Fetch AI predictions by sport, market, team, confidence
|
|
10
10
|
* get_top_picks — Get highest-conviction picks across all sports
|
|
11
|
+
* get_game — Find a specific game by team name (intelligent lookup)
|
|
12
|
+
* lookup_teams — Search/list teams by name, abbreviation, city, or sport
|
|
11
13
|
* get_pick_card — Render a pick as a shareable PNG (partner tier)
|
|
12
|
-
*
|
|
14
|
+
* get_batch_cards — Render multiple pick cards in batch
|
|
15
|
+
* get_performance — Active predictions snapshot
|
|
16
|
+
* get_track_record — Verified win rates, ROI, streaks from settled picks
|
|
17
|
+
* get_schedule — Today's games with prediction coverage
|
|
13
18
|
*
|
|
14
19
|
* Auth: DVB_API_KEY env var (dvb_live_xxx from Settings → API tab)
|
|
15
20
|
* Docs: https://www.davincibets.io/features
|
|
@@ -18,13 +23,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
18
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
24
|
import { z } from "zod";
|
|
20
25
|
// ─── Configuration ──────────────────────────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
const ALLOWED_API_HOSTS = new Set([
|
|
23
|
-
"www.davincibets.io",
|
|
24
|
-
"davincibets.io",
|
|
25
|
-
"localhost",
|
|
26
|
-
"127.0.0.1",
|
|
27
|
-
]);
|
|
26
|
+
const ALLOWED_API_HOSTS = new Set(["www.davincibets.io", "davincibets.io"]);
|
|
28
27
|
const _rawUrl = process.env.DVB_API_URL || "https://www.davincibets.io";
|
|
29
28
|
try {
|
|
30
29
|
const parsed = new URL(_rawUrl);
|
|
@@ -52,6 +51,1062 @@ if (!_rawKey.startsWith("dvb_live_")) {
|
|
|
52
51
|
process.exit(1);
|
|
53
52
|
}
|
|
54
53
|
const API_KEY = _rawKey;
|
|
54
|
+
const TEAMS = [
|
|
55
|
+
// ── NBA (30) ────────────────────────────────────────────────────────────
|
|
56
|
+
{
|
|
57
|
+
abbr: "ATL",
|
|
58
|
+
nickname: "Hawks",
|
|
59
|
+
city: "Atlanta",
|
|
60
|
+
fullName: "Atlanta Hawks",
|
|
61
|
+
sport: "NBA",
|
|
62
|
+
aliases: [],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
abbr: "BOS",
|
|
66
|
+
nickname: "Celtics",
|
|
67
|
+
city: "Boston",
|
|
68
|
+
fullName: "Boston Celtics",
|
|
69
|
+
sport: "NBA",
|
|
70
|
+
aliases: [],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
abbr: "BKN",
|
|
74
|
+
nickname: "Nets",
|
|
75
|
+
city: "Brooklyn",
|
|
76
|
+
fullName: "Brooklyn Nets",
|
|
77
|
+
sport: "NBA",
|
|
78
|
+
aliases: ["BRK"],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
abbr: "CHA",
|
|
82
|
+
nickname: "Hornets",
|
|
83
|
+
city: "Charlotte",
|
|
84
|
+
fullName: "Charlotte Hornets",
|
|
85
|
+
sport: "NBA",
|
|
86
|
+
aliases: ["CHH"],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
abbr: "CHI",
|
|
90
|
+
nickname: "Bulls",
|
|
91
|
+
city: "Chicago",
|
|
92
|
+
fullName: "Chicago Bulls",
|
|
93
|
+
sport: "NBA",
|
|
94
|
+
aliases: [],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
abbr: "CLE",
|
|
98
|
+
nickname: "Cavaliers",
|
|
99
|
+
city: "Cleveland",
|
|
100
|
+
fullName: "Cleveland Cavaliers",
|
|
101
|
+
sport: "NBA",
|
|
102
|
+
aliases: ["Cavs"],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
abbr: "DAL",
|
|
106
|
+
nickname: "Mavericks",
|
|
107
|
+
city: "Dallas",
|
|
108
|
+
fullName: "Dallas Mavericks",
|
|
109
|
+
sport: "NBA",
|
|
110
|
+
aliases: ["Mavs"],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
abbr: "DEN",
|
|
114
|
+
nickname: "Nuggets",
|
|
115
|
+
city: "Denver",
|
|
116
|
+
fullName: "Denver Nuggets",
|
|
117
|
+
sport: "NBA",
|
|
118
|
+
aliases: [],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
abbr: "DET",
|
|
122
|
+
nickname: "Pistons",
|
|
123
|
+
city: "Detroit",
|
|
124
|
+
fullName: "Detroit Pistons",
|
|
125
|
+
sport: "NBA",
|
|
126
|
+
aliases: [],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
abbr: "GSW",
|
|
130
|
+
nickname: "Warriors",
|
|
131
|
+
city: "Golden State",
|
|
132
|
+
fullName: "Golden State Warriors",
|
|
133
|
+
sport: "NBA",
|
|
134
|
+
aliases: ["GS", "SF Warriors"],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
abbr: "HOU",
|
|
138
|
+
nickname: "Rockets",
|
|
139
|
+
city: "Houston",
|
|
140
|
+
fullName: "Houston Rockets",
|
|
141
|
+
sport: "NBA",
|
|
142
|
+
aliases: [],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
abbr: "IND",
|
|
146
|
+
nickname: "Pacers",
|
|
147
|
+
city: "Indiana",
|
|
148
|
+
fullName: "Indiana Pacers",
|
|
149
|
+
sport: "NBA",
|
|
150
|
+
aliases: ["Indianapolis"],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
abbr: "LAC",
|
|
154
|
+
nickname: "Clippers",
|
|
155
|
+
city: "Los Angeles",
|
|
156
|
+
fullName: "Los Angeles Clippers",
|
|
157
|
+
sport: "NBA",
|
|
158
|
+
aliases: ["LA Clippers"],
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
abbr: "LAL",
|
|
162
|
+
nickname: "Lakers",
|
|
163
|
+
city: "Los Angeles",
|
|
164
|
+
fullName: "Los Angeles Lakers",
|
|
165
|
+
sport: "NBA",
|
|
166
|
+
aliases: ["LA Lakers"],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
abbr: "MEM",
|
|
170
|
+
nickname: "Grizzlies",
|
|
171
|
+
city: "Memphis",
|
|
172
|
+
fullName: "Memphis Grizzlies",
|
|
173
|
+
sport: "NBA",
|
|
174
|
+
aliases: ["Grizz"],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
abbr: "MIA",
|
|
178
|
+
nickname: "Heat",
|
|
179
|
+
city: "Miami",
|
|
180
|
+
fullName: "Miami Heat",
|
|
181
|
+
sport: "NBA",
|
|
182
|
+
aliases: [],
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
abbr: "MIL",
|
|
186
|
+
nickname: "Bucks",
|
|
187
|
+
city: "Milwaukee",
|
|
188
|
+
fullName: "Milwaukee Bucks",
|
|
189
|
+
sport: "NBA",
|
|
190
|
+
aliases: [],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
abbr: "MIN",
|
|
194
|
+
nickname: "Timberwolves",
|
|
195
|
+
city: "Minnesota",
|
|
196
|
+
fullName: "Minnesota Timberwolves",
|
|
197
|
+
sport: "NBA",
|
|
198
|
+
aliases: ["Wolves", "TWolves", "Minneapolis"],
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
abbr: "NOP",
|
|
202
|
+
nickname: "Pelicans",
|
|
203
|
+
city: "New Orleans",
|
|
204
|
+
fullName: "New Orleans Pelicans",
|
|
205
|
+
sport: "NBA",
|
|
206
|
+
aliases: ["NO", "NOLA", "Pels"],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
abbr: "NYK",
|
|
210
|
+
nickname: "Knicks",
|
|
211
|
+
city: "New York",
|
|
212
|
+
fullName: "New York Knicks",
|
|
213
|
+
sport: "NBA",
|
|
214
|
+
aliases: ["NY Knicks"],
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
abbr: "OKC",
|
|
218
|
+
nickname: "Thunder",
|
|
219
|
+
city: "Oklahoma City",
|
|
220
|
+
fullName: "Oklahoma City Thunder",
|
|
221
|
+
sport: "NBA",
|
|
222
|
+
aliases: [],
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
abbr: "ORL",
|
|
226
|
+
nickname: "Magic",
|
|
227
|
+
city: "Orlando",
|
|
228
|
+
fullName: "Orlando Magic",
|
|
229
|
+
sport: "NBA",
|
|
230
|
+
aliases: [],
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
abbr: "PHI",
|
|
234
|
+
nickname: "76ers",
|
|
235
|
+
city: "Philadelphia",
|
|
236
|
+
fullName: "Philadelphia 76ers",
|
|
237
|
+
sport: "NBA",
|
|
238
|
+
aliases: ["Sixers", "Philly"],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
abbr: "PHX",
|
|
242
|
+
nickname: "Suns",
|
|
243
|
+
city: "Phoenix",
|
|
244
|
+
fullName: "Phoenix Suns",
|
|
245
|
+
sport: "NBA",
|
|
246
|
+
aliases: ["PHO"],
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
abbr: "POR",
|
|
250
|
+
nickname: "Trail Blazers",
|
|
251
|
+
city: "Portland",
|
|
252
|
+
fullName: "Portland Trail Blazers",
|
|
253
|
+
sport: "NBA",
|
|
254
|
+
aliases: ["Blazers"],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
abbr: "SAC",
|
|
258
|
+
nickname: "Kings",
|
|
259
|
+
city: "Sacramento",
|
|
260
|
+
fullName: "Sacramento Kings",
|
|
261
|
+
sport: "NBA",
|
|
262
|
+
aliases: [],
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
abbr: "SAS",
|
|
266
|
+
nickname: "Spurs",
|
|
267
|
+
city: "San Antonio",
|
|
268
|
+
fullName: "San Antonio Spurs",
|
|
269
|
+
sport: "NBA",
|
|
270
|
+
aliases: ["SA"],
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
abbr: "TOR",
|
|
274
|
+
nickname: "Raptors",
|
|
275
|
+
city: "Toronto",
|
|
276
|
+
fullName: "Toronto Raptors",
|
|
277
|
+
sport: "NBA",
|
|
278
|
+
aliases: ["Raps"],
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
abbr: "UTA",
|
|
282
|
+
nickname: "Jazz",
|
|
283
|
+
city: "Utah",
|
|
284
|
+
fullName: "Utah Jazz",
|
|
285
|
+
sport: "NBA",
|
|
286
|
+
aliases: ["SLC", "Salt Lake"],
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
abbr: "WAS",
|
|
290
|
+
nickname: "Wizards",
|
|
291
|
+
city: "Washington",
|
|
292
|
+
fullName: "Washington Wizards",
|
|
293
|
+
sport: "NBA",
|
|
294
|
+
aliases: ["DC", "Wiz"],
|
|
295
|
+
},
|
|
296
|
+
// ── NFL (32) ────────────────────────────────────────────────────────────
|
|
297
|
+
{
|
|
298
|
+
abbr: "ARI",
|
|
299
|
+
nickname: "Cardinals",
|
|
300
|
+
city: "Arizona",
|
|
301
|
+
fullName: "Arizona Cardinals",
|
|
302
|
+
sport: "NFL",
|
|
303
|
+
aliases: ["Phoenix"],
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
abbr: "ATL",
|
|
307
|
+
nickname: "Falcons",
|
|
308
|
+
city: "Atlanta",
|
|
309
|
+
fullName: "Atlanta Falcons",
|
|
310
|
+
sport: "NFL",
|
|
311
|
+
aliases: [],
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
abbr: "BAL",
|
|
315
|
+
nickname: "Ravens",
|
|
316
|
+
city: "Baltimore",
|
|
317
|
+
fullName: "Baltimore Ravens",
|
|
318
|
+
sport: "NFL",
|
|
319
|
+
aliases: [],
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
abbr: "BUF",
|
|
323
|
+
nickname: "Bills",
|
|
324
|
+
city: "Buffalo",
|
|
325
|
+
fullName: "Buffalo Bills",
|
|
326
|
+
sport: "NFL",
|
|
327
|
+
aliases: [],
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
abbr: "CAR",
|
|
331
|
+
nickname: "Panthers",
|
|
332
|
+
city: "Carolina",
|
|
333
|
+
fullName: "Carolina Panthers",
|
|
334
|
+
sport: "NFL",
|
|
335
|
+
aliases: ["Charlotte"],
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
abbr: "CHI",
|
|
339
|
+
nickname: "Bears",
|
|
340
|
+
city: "Chicago",
|
|
341
|
+
fullName: "Chicago Bears",
|
|
342
|
+
sport: "NFL",
|
|
343
|
+
aliases: [],
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
abbr: "CIN",
|
|
347
|
+
nickname: "Bengals",
|
|
348
|
+
city: "Cincinnati",
|
|
349
|
+
fullName: "Cincinnati Bengals",
|
|
350
|
+
sport: "NFL",
|
|
351
|
+
aliases: ["Cincy"],
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
abbr: "CLE",
|
|
355
|
+
nickname: "Browns",
|
|
356
|
+
city: "Cleveland",
|
|
357
|
+
fullName: "Cleveland Browns",
|
|
358
|
+
sport: "NFL",
|
|
359
|
+
aliases: [],
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
abbr: "DAL",
|
|
363
|
+
nickname: "Cowboys",
|
|
364
|
+
city: "Dallas",
|
|
365
|
+
fullName: "Dallas Cowboys",
|
|
366
|
+
sport: "NFL",
|
|
367
|
+
aliases: ["America's Team"],
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
abbr: "DEN",
|
|
371
|
+
nickname: "Broncos",
|
|
372
|
+
city: "Denver",
|
|
373
|
+
fullName: "Denver Broncos",
|
|
374
|
+
sport: "NFL",
|
|
375
|
+
aliases: [],
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
abbr: "DET",
|
|
379
|
+
nickname: "Lions",
|
|
380
|
+
city: "Detroit",
|
|
381
|
+
fullName: "Detroit Lions",
|
|
382
|
+
sport: "NFL",
|
|
383
|
+
aliases: [],
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
abbr: "GB",
|
|
387
|
+
nickname: "Packers",
|
|
388
|
+
city: "Green Bay",
|
|
389
|
+
fullName: "Green Bay Packers",
|
|
390
|
+
sport: "NFL",
|
|
391
|
+
aliases: ["GNB"],
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
abbr: "HOU",
|
|
395
|
+
nickname: "Texans",
|
|
396
|
+
city: "Houston",
|
|
397
|
+
fullName: "Houston Texans",
|
|
398
|
+
sport: "NFL",
|
|
399
|
+
aliases: [],
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
abbr: "IND",
|
|
403
|
+
nickname: "Colts",
|
|
404
|
+
city: "Indianapolis",
|
|
405
|
+
fullName: "Indianapolis Colts",
|
|
406
|
+
sport: "NFL",
|
|
407
|
+
aliases: [],
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
abbr: "JAX",
|
|
411
|
+
nickname: "Jaguars",
|
|
412
|
+
city: "Jacksonville",
|
|
413
|
+
fullName: "Jacksonville Jaguars",
|
|
414
|
+
sport: "NFL",
|
|
415
|
+
aliases: ["Jags", "JAC"],
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
abbr: "KC",
|
|
419
|
+
nickname: "Chiefs",
|
|
420
|
+
city: "Kansas City",
|
|
421
|
+
fullName: "Kansas City Chiefs",
|
|
422
|
+
sport: "NFL",
|
|
423
|
+
aliases: ["KCC"],
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
abbr: "LV",
|
|
427
|
+
nickname: "Raiders",
|
|
428
|
+
city: "Las Vegas",
|
|
429
|
+
fullName: "Las Vegas Raiders",
|
|
430
|
+
sport: "NFL",
|
|
431
|
+
aliases: ["LVR", "Oakland"],
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
abbr: "LAC",
|
|
435
|
+
nickname: "Chargers",
|
|
436
|
+
city: "Los Angeles",
|
|
437
|
+
fullName: "Los Angeles Chargers",
|
|
438
|
+
sport: "NFL",
|
|
439
|
+
aliases: ["LA Chargers", "San Diego"],
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
abbr: "LAR",
|
|
443
|
+
nickname: "Rams",
|
|
444
|
+
city: "Los Angeles",
|
|
445
|
+
fullName: "Los Angeles Rams",
|
|
446
|
+
sport: "NFL",
|
|
447
|
+
aliases: ["LA Rams", "STL Rams"],
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
abbr: "MIA",
|
|
451
|
+
nickname: "Dolphins",
|
|
452
|
+
city: "Miami",
|
|
453
|
+
fullName: "Miami Dolphins",
|
|
454
|
+
sport: "NFL",
|
|
455
|
+
aliases: ["Fins"],
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
abbr: "MIN",
|
|
459
|
+
nickname: "Vikings",
|
|
460
|
+
city: "Minnesota",
|
|
461
|
+
fullName: "Minnesota Vikings",
|
|
462
|
+
sport: "NFL",
|
|
463
|
+
aliases: ["Minneapolis"],
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
abbr: "NE",
|
|
467
|
+
nickname: "Patriots",
|
|
468
|
+
city: "New England",
|
|
469
|
+
fullName: "New England Patriots",
|
|
470
|
+
sport: "NFL",
|
|
471
|
+
aliases: ["Pats", "NEP", "Boston Patriots"],
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
abbr: "NO",
|
|
475
|
+
nickname: "Saints",
|
|
476
|
+
city: "New Orleans",
|
|
477
|
+
fullName: "New Orleans Saints",
|
|
478
|
+
sport: "NFL",
|
|
479
|
+
aliases: ["NOLA", "NOS"],
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
abbr: "NYG",
|
|
483
|
+
nickname: "Giants",
|
|
484
|
+
city: "New York",
|
|
485
|
+
fullName: "New York Giants",
|
|
486
|
+
sport: "NFL",
|
|
487
|
+
aliases: ["NY Giants"],
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
abbr: "NYJ",
|
|
491
|
+
nickname: "Jets",
|
|
492
|
+
city: "New York",
|
|
493
|
+
fullName: "New York Jets",
|
|
494
|
+
sport: "NFL",
|
|
495
|
+
aliases: ["NY Jets"],
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
abbr: "PHI",
|
|
499
|
+
nickname: "Eagles",
|
|
500
|
+
city: "Philadelphia",
|
|
501
|
+
fullName: "Philadelphia Eagles",
|
|
502
|
+
sport: "NFL",
|
|
503
|
+
aliases: ["Philly"],
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
abbr: "PIT",
|
|
507
|
+
nickname: "Steelers",
|
|
508
|
+
city: "Pittsburgh",
|
|
509
|
+
fullName: "Pittsburgh Steelers",
|
|
510
|
+
sport: "NFL",
|
|
511
|
+
aliases: [],
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
abbr: "SF",
|
|
515
|
+
nickname: "49ers",
|
|
516
|
+
city: "San Francisco",
|
|
517
|
+
fullName: "San Francisco 49ers",
|
|
518
|
+
sport: "NFL",
|
|
519
|
+
aliases: ["Niners", "SFO"],
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
abbr: "SEA",
|
|
523
|
+
nickname: "Seahawks",
|
|
524
|
+
city: "Seattle",
|
|
525
|
+
fullName: "Seattle Seahawks",
|
|
526
|
+
sport: "NFL",
|
|
527
|
+
aliases: [],
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
abbr: "TB",
|
|
531
|
+
nickname: "Buccaneers",
|
|
532
|
+
city: "Tampa Bay",
|
|
533
|
+
fullName: "Tampa Bay Buccaneers",
|
|
534
|
+
sport: "NFL",
|
|
535
|
+
aliases: ["Bucs", "TBB", "Tampa"],
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
abbr: "TEN",
|
|
539
|
+
nickname: "Titans",
|
|
540
|
+
city: "Tennessee",
|
|
541
|
+
fullName: "Tennessee Titans",
|
|
542
|
+
sport: "NFL",
|
|
543
|
+
aliases: ["Nashville"],
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
abbr: "WAS",
|
|
547
|
+
nickname: "Commanders",
|
|
548
|
+
city: "Washington",
|
|
549
|
+
fullName: "Washington Commanders",
|
|
550
|
+
sport: "NFL",
|
|
551
|
+
aliases: ["DC", "WSH"],
|
|
552
|
+
},
|
|
553
|
+
// ── NHL (32) ────────────────────────────────────────────────────────────
|
|
554
|
+
{
|
|
555
|
+
abbr: "ANA",
|
|
556
|
+
nickname: "Ducks",
|
|
557
|
+
city: "Anaheim",
|
|
558
|
+
fullName: "Anaheim Ducks",
|
|
559
|
+
sport: "NHL",
|
|
560
|
+
aliases: [],
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
abbr: "BOS",
|
|
564
|
+
nickname: "Bruins",
|
|
565
|
+
city: "Boston",
|
|
566
|
+
fullName: "Boston Bruins",
|
|
567
|
+
sport: "NHL",
|
|
568
|
+
aliases: [],
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
abbr: "BUF",
|
|
572
|
+
nickname: "Sabres",
|
|
573
|
+
city: "Buffalo",
|
|
574
|
+
fullName: "Buffalo Sabres",
|
|
575
|
+
sport: "NHL",
|
|
576
|
+
aliases: [],
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
abbr: "CGY",
|
|
580
|
+
nickname: "Flames",
|
|
581
|
+
city: "Calgary",
|
|
582
|
+
fullName: "Calgary Flames",
|
|
583
|
+
sport: "NHL",
|
|
584
|
+
aliases: ["CAL"],
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
abbr: "CAR",
|
|
588
|
+
nickname: "Hurricanes",
|
|
589
|
+
city: "Carolina",
|
|
590
|
+
fullName: "Carolina Hurricanes",
|
|
591
|
+
sport: "NHL",
|
|
592
|
+
aliases: ["Canes", "Raleigh"],
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
abbr: "CHI",
|
|
596
|
+
nickname: "Blackhawks",
|
|
597
|
+
city: "Chicago",
|
|
598
|
+
fullName: "Chicago Blackhawks",
|
|
599
|
+
sport: "NHL",
|
|
600
|
+
aliases: ["Hawks"],
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
abbr: "COL",
|
|
604
|
+
nickname: "Avalanche",
|
|
605
|
+
city: "Colorado",
|
|
606
|
+
fullName: "Colorado Avalanche",
|
|
607
|
+
sport: "NHL",
|
|
608
|
+
aliases: ["Avs", "Denver"],
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
abbr: "CBJ",
|
|
612
|
+
nickname: "Blue Jackets",
|
|
613
|
+
city: "Columbus",
|
|
614
|
+
fullName: "Columbus Blue Jackets",
|
|
615
|
+
sport: "NHL",
|
|
616
|
+
aliases: ["Jackets"],
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
abbr: "DAL",
|
|
620
|
+
nickname: "Stars",
|
|
621
|
+
city: "Dallas",
|
|
622
|
+
fullName: "Dallas Stars",
|
|
623
|
+
sport: "NHL",
|
|
624
|
+
aliases: [],
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
abbr: "DET",
|
|
628
|
+
nickname: "Red Wings",
|
|
629
|
+
city: "Detroit",
|
|
630
|
+
fullName: "Detroit Red Wings",
|
|
631
|
+
sport: "NHL",
|
|
632
|
+
aliases: ["Wings"],
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
abbr: "EDM",
|
|
636
|
+
nickname: "Oilers",
|
|
637
|
+
city: "Edmonton",
|
|
638
|
+
fullName: "Edmonton Oilers",
|
|
639
|
+
sport: "NHL",
|
|
640
|
+
aliases: [],
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
abbr: "FLA",
|
|
644
|
+
nickname: "Panthers",
|
|
645
|
+
city: "Florida",
|
|
646
|
+
fullName: "Florida Panthers",
|
|
647
|
+
sport: "NHL",
|
|
648
|
+
aliases: ["Cats", "Sunrise"],
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
abbr: "LAK",
|
|
652
|
+
nickname: "Kings",
|
|
653
|
+
city: "Los Angeles",
|
|
654
|
+
fullName: "Los Angeles Kings",
|
|
655
|
+
sport: "NHL",
|
|
656
|
+
aliases: ["LA Kings"],
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
abbr: "MIN",
|
|
660
|
+
nickname: "Wild",
|
|
661
|
+
city: "Minnesota",
|
|
662
|
+
fullName: "Minnesota Wild",
|
|
663
|
+
sport: "NHL",
|
|
664
|
+
aliases: ["Minneapolis", "St Paul"],
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
abbr: "MTL",
|
|
668
|
+
nickname: "Canadiens",
|
|
669
|
+
city: "Montreal",
|
|
670
|
+
fullName: "Montreal Canadiens",
|
|
671
|
+
sport: "NHL",
|
|
672
|
+
aliases: ["Habs", "MON"],
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
abbr: "NSH",
|
|
676
|
+
nickname: "Predators",
|
|
677
|
+
city: "Nashville",
|
|
678
|
+
fullName: "Nashville Predators",
|
|
679
|
+
sport: "NHL",
|
|
680
|
+
aliases: ["Preds"],
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
abbr: "NJD",
|
|
684
|
+
nickname: "Devils",
|
|
685
|
+
city: "New Jersey",
|
|
686
|
+
fullName: "New Jersey Devils",
|
|
687
|
+
sport: "NHL",
|
|
688
|
+
aliases: ["NJ", "Newark"],
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
abbr: "NYI",
|
|
692
|
+
nickname: "Islanders",
|
|
693
|
+
city: "New York",
|
|
694
|
+
fullName: "New York Islanders",
|
|
695
|
+
sport: "NHL",
|
|
696
|
+
aliases: ["Isles", "NY Islanders"],
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
abbr: "NYR",
|
|
700
|
+
nickname: "Rangers",
|
|
701
|
+
city: "New York",
|
|
702
|
+
fullName: "New York Rangers",
|
|
703
|
+
sport: "NHL",
|
|
704
|
+
aliases: ["NY Rangers"],
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
abbr: "OTT",
|
|
708
|
+
nickname: "Senators",
|
|
709
|
+
city: "Ottawa",
|
|
710
|
+
fullName: "Ottawa Senators",
|
|
711
|
+
sport: "NHL",
|
|
712
|
+
aliases: ["Sens"],
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
abbr: "PHI",
|
|
716
|
+
nickname: "Flyers",
|
|
717
|
+
city: "Philadelphia",
|
|
718
|
+
fullName: "Philadelphia Flyers",
|
|
719
|
+
sport: "NHL",
|
|
720
|
+
aliases: ["Philly"],
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
abbr: "PIT",
|
|
724
|
+
nickname: "Penguins",
|
|
725
|
+
city: "Pittsburgh",
|
|
726
|
+
fullName: "Pittsburgh Penguins",
|
|
727
|
+
sport: "NHL",
|
|
728
|
+
aliases: ["Pens"],
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
abbr: "SJS",
|
|
732
|
+
nickname: "Sharks",
|
|
733
|
+
city: "San Jose",
|
|
734
|
+
fullName: "San Jose Sharks",
|
|
735
|
+
sport: "NHL",
|
|
736
|
+
aliases: ["SJ"],
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
abbr: "SEA",
|
|
740
|
+
nickname: "Kraken",
|
|
741
|
+
city: "Seattle",
|
|
742
|
+
fullName: "Seattle Kraken",
|
|
743
|
+
sport: "NHL",
|
|
744
|
+
aliases: [],
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
abbr: "STL",
|
|
748
|
+
nickname: "Blues",
|
|
749
|
+
city: "St. Louis",
|
|
750
|
+
fullName: "St. Louis Blues",
|
|
751
|
+
sport: "NHL",
|
|
752
|
+
aliases: ["Saint Louis", "St Louis"],
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
abbr: "TBL",
|
|
756
|
+
nickname: "Lightning",
|
|
757
|
+
city: "Tampa Bay",
|
|
758
|
+
fullName: "Tampa Bay Lightning",
|
|
759
|
+
sport: "NHL",
|
|
760
|
+
aliases: ["Bolts", "TB", "Tampa"],
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
abbr: "TOR",
|
|
764
|
+
nickname: "Maple Leafs",
|
|
765
|
+
city: "Toronto",
|
|
766
|
+
fullName: "Toronto Maple Leafs",
|
|
767
|
+
sport: "NHL",
|
|
768
|
+
aliases: ["Leafs"],
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
abbr: "VAN",
|
|
772
|
+
nickname: "Canucks",
|
|
773
|
+
city: "Vancouver",
|
|
774
|
+
fullName: "Vancouver Canucks",
|
|
775
|
+
sport: "NHL",
|
|
776
|
+
aliases: [],
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
abbr: "VGK",
|
|
780
|
+
nickname: "Golden Knights",
|
|
781
|
+
city: "Vegas",
|
|
782
|
+
fullName: "Vegas Golden Knights",
|
|
783
|
+
sport: "NHL",
|
|
784
|
+
aliases: ["VGS", "Las Vegas", "Knights"],
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
abbr: "WAS",
|
|
788
|
+
nickname: "Capitals",
|
|
789
|
+
city: "Washington",
|
|
790
|
+
fullName: "Washington Capitals",
|
|
791
|
+
sport: "NHL",
|
|
792
|
+
aliases: ["DC", "WSH", "Caps"],
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
abbr: "WPG",
|
|
796
|
+
nickname: "Jets",
|
|
797
|
+
city: "Winnipeg",
|
|
798
|
+
fullName: "Winnipeg Jets",
|
|
799
|
+
sport: "NHL",
|
|
800
|
+
aliases: ["WIN"],
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
abbr: "UTA",
|
|
804
|
+
nickname: "Hockey Club",
|
|
805
|
+
city: "Utah",
|
|
806
|
+
fullName: "Utah Hockey Club",
|
|
807
|
+
sport: "NHL",
|
|
808
|
+
aliases: ["UHC", "Salt Lake"],
|
|
809
|
+
},
|
|
810
|
+
// ── MLB (30) ────────────────────────────────────────────────────────────
|
|
811
|
+
{
|
|
812
|
+
abbr: "ARI",
|
|
813
|
+
nickname: "Diamondbacks",
|
|
814
|
+
city: "Arizona",
|
|
815
|
+
fullName: "Arizona Diamondbacks",
|
|
816
|
+
sport: "MLB",
|
|
817
|
+
aliases: ["D-backs", "Dbacks", "Phoenix"],
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
abbr: "ATL",
|
|
821
|
+
nickname: "Braves",
|
|
822
|
+
city: "Atlanta",
|
|
823
|
+
fullName: "Atlanta Braves",
|
|
824
|
+
sport: "MLB",
|
|
825
|
+
aliases: [],
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
abbr: "BAL",
|
|
829
|
+
nickname: "Orioles",
|
|
830
|
+
city: "Baltimore",
|
|
831
|
+
fullName: "Baltimore Orioles",
|
|
832
|
+
sport: "MLB",
|
|
833
|
+
aliases: ["O's"],
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
abbr: "BOS",
|
|
837
|
+
nickname: "Red Sox",
|
|
838
|
+
city: "Boston",
|
|
839
|
+
fullName: "Boston Red Sox",
|
|
840
|
+
sport: "MLB",
|
|
841
|
+
aliases: ["Sox", "BoSox"],
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
abbr: "CHC",
|
|
845
|
+
nickname: "Cubs",
|
|
846
|
+
city: "Chicago",
|
|
847
|
+
fullName: "Chicago Cubs",
|
|
848
|
+
sport: "MLB",
|
|
849
|
+
aliases: [],
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
abbr: "CHW",
|
|
853
|
+
nickname: "White Sox",
|
|
854
|
+
city: "Chicago",
|
|
855
|
+
fullName: "Chicago White Sox",
|
|
856
|
+
sport: "MLB",
|
|
857
|
+
aliases: ["CWS", "Sox"],
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
abbr: "CIN",
|
|
861
|
+
nickname: "Reds",
|
|
862
|
+
city: "Cincinnati",
|
|
863
|
+
fullName: "Cincinnati Reds",
|
|
864
|
+
sport: "MLB",
|
|
865
|
+
aliases: ["Cincy"],
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
abbr: "CLE",
|
|
869
|
+
nickname: "Guardians",
|
|
870
|
+
city: "Cleveland",
|
|
871
|
+
fullName: "Cleveland Guardians",
|
|
872
|
+
sport: "MLB",
|
|
873
|
+
aliases: ["Guards"],
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
abbr: "COL",
|
|
877
|
+
nickname: "Rockies",
|
|
878
|
+
city: "Colorado",
|
|
879
|
+
fullName: "Colorado Rockies",
|
|
880
|
+
sport: "MLB",
|
|
881
|
+
aliases: ["Denver"],
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
abbr: "DET",
|
|
885
|
+
nickname: "Tigers",
|
|
886
|
+
city: "Detroit",
|
|
887
|
+
fullName: "Detroit Tigers",
|
|
888
|
+
sport: "MLB",
|
|
889
|
+
aliases: [],
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
abbr: "HOU",
|
|
893
|
+
nickname: "Astros",
|
|
894
|
+
city: "Houston",
|
|
895
|
+
fullName: "Houston Astros",
|
|
896
|
+
sport: "MLB",
|
|
897
|
+
aliases: ["Stros"],
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
abbr: "KC",
|
|
901
|
+
nickname: "Royals",
|
|
902
|
+
city: "Kansas City",
|
|
903
|
+
fullName: "Kansas City Royals",
|
|
904
|
+
sport: "MLB",
|
|
905
|
+
aliases: ["KCR"],
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
abbr: "LAA",
|
|
909
|
+
nickname: "Angels",
|
|
910
|
+
city: "Los Angeles",
|
|
911
|
+
fullName: "Los Angeles Angels",
|
|
912
|
+
sport: "MLB",
|
|
913
|
+
aliases: ["LA Angels", "Anaheim", "Halos"],
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
abbr: "LAD",
|
|
917
|
+
nickname: "Dodgers",
|
|
918
|
+
city: "Los Angeles",
|
|
919
|
+
fullName: "Los Angeles Dodgers",
|
|
920
|
+
sport: "MLB",
|
|
921
|
+
aliases: ["LA Dodgers"],
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
abbr: "MIA",
|
|
925
|
+
nickname: "Marlins",
|
|
926
|
+
city: "Miami",
|
|
927
|
+
fullName: "Miami Marlins",
|
|
928
|
+
sport: "MLB",
|
|
929
|
+
aliases: ["Fish"],
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
abbr: "MIL",
|
|
933
|
+
nickname: "Brewers",
|
|
934
|
+
city: "Milwaukee",
|
|
935
|
+
fullName: "Milwaukee Brewers",
|
|
936
|
+
sport: "MLB",
|
|
937
|
+
aliases: ["Brew Crew"],
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
abbr: "MIN",
|
|
941
|
+
nickname: "Twins",
|
|
942
|
+
city: "Minnesota",
|
|
943
|
+
fullName: "Minnesota Twins",
|
|
944
|
+
sport: "MLB",
|
|
945
|
+
aliases: ["Minneapolis"],
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
abbr: "NYM",
|
|
949
|
+
nickname: "Mets",
|
|
950
|
+
city: "New York",
|
|
951
|
+
fullName: "New York Mets",
|
|
952
|
+
sport: "MLB",
|
|
953
|
+
aliases: ["NY Mets"],
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
abbr: "NYY",
|
|
957
|
+
nickname: "Yankees",
|
|
958
|
+
city: "New York",
|
|
959
|
+
fullName: "New York Yankees",
|
|
960
|
+
sport: "MLB",
|
|
961
|
+
aliases: ["NY Yankees", "Yanks", "Bronx Bombers"],
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
abbr: "OAK",
|
|
965
|
+
nickname: "Athletics",
|
|
966
|
+
city: "Oakland",
|
|
967
|
+
fullName: "Oakland Athletics",
|
|
968
|
+
sport: "MLB",
|
|
969
|
+
aliases: ["A's", "As"],
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
abbr: "PHI",
|
|
973
|
+
nickname: "Phillies",
|
|
974
|
+
city: "Philadelphia",
|
|
975
|
+
fullName: "Philadelphia Phillies",
|
|
976
|
+
sport: "MLB",
|
|
977
|
+
aliases: ["Philly"],
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
abbr: "PIT",
|
|
981
|
+
nickname: "Pirates",
|
|
982
|
+
city: "Pittsburgh",
|
|
983
|
+
fullName: "Pittsburgh Pirates",
|
|
984
|
+
sport: "MLB",
|
|
985
|
+
aliases: ["Bucs"],
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
abbr: "SD",
|
|
989
|
+
nickname: "Padres",
|
|
990
|
+
city: "San Diego",
|
|
991
|
+
fullName: "San Diego Padres",
|
|
992
|
+
sport: "MLB",
|
|
993
|
+
aliases: ["SDP"],
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
abbr: "SF",
|
|
997
|
+
nickname: "Giants",
|
|
998
|
+
city: "San Francisco",
|
|
999
|
+
fullName: "San Francisco Giants",
|
|
1000
|
+
sport: "MLB",
|
|
1001
|
+
aliases: ["SFG"],
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
abbr: "SEA",
|
|
1005
|
+
nickname: "Mariners",
|
|
1006
|
+
city: "Seattle",
|
|
1007
|
+
fullName: "Seattle Mariners",
|
|
1008
|
+
sport: "MLB",
|
|
1009
|
+
aliases: ["M's", "Ms"],
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
abbr: "STL",
|
|
1013
|
+
nickname: "Cardinals",
|
|
1014
|
+
city: "St. Louis",
|
|
1015
|
+
fullName: "St. Louis Cardinals",
|
|
1016
|
+
sport: "MLB",
|
|
1017
|
+
aliases: ["Cards", "Saint Louis", "St Louis"],
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
abbr: "TB",
|
|
1021
|
+
nickname: "Rays",
|
|
1022
|
+
city: "Tampa Bay",
|
|
1023
|
+
fullName: "Tampa Bay Rays",
|
|
1024
|
+
sport: "MLB",
|
|
1025
|
+
aliases: ["TBR", "Tampa"],
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
abbr: "TEX",
|
|
1029
|
+
nickname: "Rangers",
|
|
1030
|
+
city: "Texas",
|
|
1031
|
+
fullName: "Texas Rangers",
|
|
1032
|
+
sport: "MLB",
|
|
1033
|
+
aliases: ["Arlington"],
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
abbr: "TOR",
|
|
1037
|
+
nickname: "Blue Jays",
|
|
1038
|
+
city: "Toronto",
|
|
1039
|
+
fullName: "Toronto Blue Jays",
|
|
1040
|
+
sport: "MLB",
|
|
1041
|
+
aliases: ["Jays"],
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
abbr: "WAS",
|
|
1045
|
+
nickname: "Nationals",
|
|
1046
|
+
city: "Washington",
|
|
1047
|
+
fullName: "Washington Nationals",
|
|
1048
|
+
sport: "MLB",
|
|
1049
|
+
aliases: ["DC", "WSH", "Nats"],
|
|
1050
|
+
},
|
|
1051
|
+
];
|
|
1052
|
+
/**
|
|
1053
|
+
* Fuzzy team search — matches query against abbreviation, nickname, city,
|
|
1054
|
+
* fullName, and aliases. Returns scored results sorted by match quality.
|
|
1055
|
+
*/
|
|
1056
|
+
function searchTeams(query, sportFilter) {
|
|
1057
|
+
const q = query.toLowerCase().trim();
|
|
1058
|
+
if (!q)
|
|
1059
|
+
return [];
|
|
1060
|
+
const results = [];
|
|
1061
|
+
for (const team of TEAMS) {
|
|
1062
|
+
if (sportFilter && team.sport !== sportFilter)
|
|
1063
|
+
continue;
|
|
1064
|
+
let score = 0;
|
|
1065
|
+
const abbrL = team.abbr.toLowerCase();
|
|
1066
|
+
const nickL = team.nickname.toLowerCase();
|
|
1067
|
+
const cityL = team.city.toLowerCase();
|
|
1068
|
+
const fullL = team.fullName.toLowerCase();
|
|
1069
|
+
// Exact matches (highest priority)
|
|
1070
|
+
if (abbrL === q)
|
|
1071
|
+
score = 100;
|
|
1072
|
+
else if (nickL === q)
|
|
1073
|
+
score = 95;
|
|
1074
|
+
else if (fullL === q)
|
|
1075
|
+
score = 90;
|
|
1076
|
+
else if (cityL === q)
|
|
1077
|
+
score = 85;
|
|
1078
|
+
// Alias exact match
|
|
1079
|
+
else if (team.aliases.some((a) => a.toLowerCase() === q))
|
|
1080
|
+
score = 80;
|
|
1081
|
+
// Starts-with matches
|
|
1082
|
+
else if (nickL.startsWith(q))
|
|
1083
|
+
score = 70;
|
|
1084
|
+
else if (cityL.startsWith(q))
|
|
1085
|
+
score = 65;
|
|
1086
|
+
else if (abbrL.startsWith(q))
|
|
1087
|
+
score = 60;
|
|
1088
|
+
// Contains matches
|
|
1089
|
+
else if (fullL.includes(q))
|
|
1090
|
+
score = 50;
|
|
1091
|
+
else if (nickL.includes(q))
|
|
1092
|
+
score = 45;
|
|
1093
|
+
else if (team.aliases.some((a) => a.toLowerCase().includes(q)))
|
|
1094
|
+
score = 40;
|
|
1095
|
+
if (score > 0) {
|
|
1096
|
+
results.push({ ...team, score });
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
results.sort((a, b) => b.score - a.score);
|
|
1100
|
+
return results;
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Resolve a query to the single best-matching team.
|
|
1104
|
+
* If sport is given, only matches within that sport.
|
|
1105
|
+
*/
|
|
1106
|
+
function resolveTeam(query, sport) {
|
|
1107
|
+
const matches = searchTeams(query, sport);
|
|
1108
|
+
return matches.length > 0 ? matches[0] : null;
|
|
1109
|
+
}
|
|
55
1110
|
async function apiFetch(opts) {
|
|
56
1111
|
const url = new URL(opts.path, API_BASE);
|
|
57
1112
|
if (opts.params) {
|
|
@@ -83,7 +1138,6 @@ async function apiFetch(opts) {
|
|
|
83
1138
|
const remainDay = res.headers.get("X-RateLimit-Remaining-Day") || "0";
|
|
84
1139
|
throw new Error(`Rate limit exceeded. Remaining: ${remainHour}/hour, ${remainDay}/day. Retry after ${retryAfter}s.`);
|
|
85
1140
|
}
|
|
86
|
-
// FINDING 5 FIX: Don't forward raw response body — may contain internal details
|
|
87
1141
|
if (status === 403)
|
|
88
1142
|
throw new Error("Access denied. Your subscription may have expired.");
|
|
89
1143
|
if (status === 404)
|
|
@@ -97,15 +1151,59 @@ async function apiFetch(opts) {
|
|
|
97
1151
|
}
|
|
98
1152
|
return (await res.json());
|
|
99
1153
|
}
|
|
1154
|
+
// ─── Formatting Helpers ─────────────────────────────────────────────────────
|
|
1155
|
+
function formatPick(p) {
|
|
1156
|
+
const pick = p.predicted_winner || p.predicted_pick || p.predicted_direction;
|
|
1157
|
+
const isHomePick = pick === p.home_team || pick === "HOME" || pick === "OVER";
|
|
1158
|
+
const edge = isHomePick
|
|
1159
|
+
? (p.home_edge ?? p.away_edge)
|
|
1160
|
+
: (p.away_edge ?? p.home_edge);
|
|
1161
|
+
const tier = p.confidence_tier ? ` [${p.confidence_tier}]` : "";
|
|
1162
|
+
let line = `${p.away_team} @ ${p.home_team}`;
|
|
1163
|
+
line += `\n Game ID: ${p.game_id}`;
|
|
1164
|
+
line += `\n Pick: ${pick} (${p.confidence}% conf`;
|
|
1165
|
+
if (edge)
|
|
1166
|
+
line += `, ${edge}% edge`;
|
|
1167
|
+
line += `)${tier}`;
|
|
1168
|
+
if (p.market === "spread" && p.spread_line != null) {
|
|
1169
|
+
line += `\n Line: ${p.spread_line}`;
|
|
1170
|
+
}
|
|
1171
|
+
if (p.market === "totals" && p.totals_line != null) {
|
|
1172
|
+
line += `\n Line: ${p.totals_line}`;
|
|
1173
|
+
}
|
|
1174
|
+
if (p.home_odds) {
|
|
1175
|
+
line += `\n Odds: ${p.home_odds}/${p.away_odds}`;
|
|
1176
|
+
}
|
|
1177
|
+
if (p.commence_time) {
|
|
1178
|
+
const time = new Date(p.commence_time).toLocaleString("en-US", {
|
|
1179
|
+
timeZone: "America/New_York",
|
|
1180
|
+
weekday: "short",
|
|
1181
|
+
month: "short",
|
|
1182
|
+
day: "numeric",
|
|
1183
|
+
hour: "numeric",
|
|
1184
|
+
minute: "2-digit",
|
|
1185
|
+
timeZoneName: "short",
|
|
1186
|
+
});
|
|
1187
|
+
line += `\n Game: ${time}`;
|
|
1188
|
+
}
|
|
1189
|
+
return line;
|
|
1190
|
+
}
|
|
1191
|
+
function formatRateLimit(rl) {
|
|
1192
|
+
if (!rl)
|
|
1193
|
+
return "";
|
|
1194
|
+
return `\nRate limit: ${rl.remaining_hour}/hour, ${rl.remaining_day}/day remaining.`;
|
|
1195
|
+
}
|
|
100
1196
|
// ─── Server Setup ───────────────────────────────────────────────────────────
|
|
101
1197
|
const server = new McpServer({
|
|
102
1198
|
name: "Da Vinci Bets",
|
|
103
|
-
version: "1.0
|
|
1199
|
+
version: "1.3.0",
|
|
104
1200
|
});
|
|
105
1201
|
// ─── Tool: get_predictions ──────────────────────────────────────────────────
|
|
106
|
-
server.tool("get_predictions", "Fetch AI-powered sports predictions filtered by sport, market, and confidence. " +
|
|
107
|
-
"Returns predictions with win probabilities, odds, edge values, and
|
|
108
|
-
"Covers NBA, NFL, NHL, and MLB across moneylines, spreads, and totals."
|
|
1202
|
+
server.tool("get_predictions", "Fetch AI-powered sports predictions filtered by sport, market, team, and confidence. " +
|
|
1203
|
+
"Returns predictions with win probabilities, odds, edge values, confidence tiers, and game IDs. " +
|
|
1204
|
+
"Covers NBA, NFL, NHL, and MLB across moneylines, spreads, and totals. " +
|
|
1205
|
+
"Use the team parameter to find predictions for a specific team (e.g., 'Lakers', 'NYY', 'Chiefs'). " +
|
|
1206
|
+
"Game IDs from results can be used with get_pick_card to render shareable images.", {
|
|
109
1207
|
sport: z
|
|
110
1208
|
.enum(["NBA", "NFL", "NHL", "MLB"])
|
|
111
1209
|
.optional()
|
|
@@ -114,6 +1212,12 @@ server.tool("get_predictions", "Fetch AI-powered sports predictions filtered by
|
|
|
114
1212
|
.enum(["moneyline", "spread", "totals"])
|
|
115
1213
|
.default("moneyline")
|
|
116
1214
|
.describe("Betting market type."),
|
|
1215
|
+
team: z
|
|
1216
|
+
.string()
|
|
1217
|
+
.optional()
|
|
1218
|
+
.describe("Filter by team name, abbreviation, or city. " +
|
|
1219
|
+
"Examples: 'Lakers', 'LAL', 'NYY', 'Chiefs', 'Boston'. " +
|
|
1220
|
+
"Resolved intelligently — abbreviations, nicknames, and cities all work."),
|
|
117
1221
|
sort: z
|
|
118
1222
|
.enum(["confidence", "edge", "game_time"])
|
|
119
1223
|
.default("confidence")
|
|
@@ -132,7 +1236,7 @@ server.tool("get_predictions", "Fetch AI-powered sports predictions filtered by
|
|
|
132
1236
|
.max(100)
|
|
133
1237
|
.default(60)
|
|
134
1238
|
.describe("Minimum confidence percentage (0-100)."),
|
|
135
|
-
}, async ({ sport, market, sort, limit, min_confidence }) => {
|
|
1239
|
+
}, async ({ sport, market, team, sort, limit, min_confidence }) => {
|
|
136
1240
|
try {
|
|
137
1241
|
const params = {
|
|
138
1242
|
market,
|
|
@@ -142,37 +1246,45 @@ server.tool("get_predictions", "Fetch AI-powered sports predictions filtered by
|
|
|
142
1246
|
};
|
|
143
1247
|
if (sport)
|
|
144
1248
|
params.sport = sport;
|
|
1249
|
+
// Resolve team name to canonical form for API filter
|
|
1250
|
+
if (team) {
|
|
1251
|
+
const resolved = resolveTeam(team, sport || undefined);
|
|
1252
|
+
if (resolved) {
|
|
1253
|
+
params.team = resolved.nickname;
|
|
1254
|
+
if (!sport)
|
|
1255
|
+
params.sport = resolved.sport;
|
|
1256
|
+
}
|
|
1257
|
+
else {
|
|
1258
|
+
// Pass raw query — API does ILIKE matching
|
|
1259
|
+
params.team = team;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
145
1262
|
const data = await apiFetch({
|
|
146
1263
|
path: "/api/v1/predictions",
|
|
147
1264
|
params,
|
|
148
1265
|
});
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
line += ` | Odds: ${p.home_odds}/${p.away_odds}`;
|
|
169
|
-
}
|
|
170
|
-
return line;
|
|
171
|
-
}),
|
|
172
|
-
].join("\n");
|
|
1266
|
+
if (data.predictions.length === 0) {
|
|
1267
|
+
const teamHint = team ? ` for "${team}"` : "";
|
|
1268
|
+
return {
|
|
1269
|
+
content: [
|
|
1270
|
+
{
|
|
1271
|
+
type: "text",
|
|
1272
|
+
text: `No ${market} predictions found${teamHint}${sport ? ` in ${sport}` : ""}. ` +
|
|
1273
|
+
`Games may not be scheduled, or try lowering min_confidence (currently ${min_confidence}%).`,
|
|
1274
|
+
},
|
|
1275
|
+
],
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
const lines = data.predictions.map((p, i) => `${i + 1}. ${formatPick(p)}`);
|
|
1279
|
+
const header = [
|
|
1280
|
+
`Found ${data.meta.total} ${market} predictions${sport ? ` for ${sport}` : ""}${team ? ` matching "${team}"` : ""}.`,
|
|
1281
|
+
formatRateLimit(data.rate_limit),
|
|
1282
|
+
]
|
|
1283
|
+
.filter(Boolean)
|
|
1284
|
+
.join("");
|
|
173
1285
|
return {
|
|
174
1286
|
content: [
|
|
175
|
-
{ type: "text", text:
|
|
1287
|
+
{ type: "text", text: header + "\n\n" + lines.join("\n\n") },
|
|
176
1288
|
{
|
|
177
1289
|
type: "text",
|
|
178
1290
|
text: "\n---\nRaw JSON:\n" + JSON.stringify(data.predictions, null, 2),
|
|
@@ -184,10 +1296,196 @@ server.tool("get_predictions", "Fetch AI-powered sports predictions filtered by
|
|
|
184
1296
|
return errorResult(err);
|
|
185
1297
|
}
|
|
186
1298
|
});
|
|
1299
|
+
// ─── Tool: get_game ─────────────────────────────────────────────────────────
|
|
1300
|
+
server.tool("get_game", "Find a specific game's prediction by team name. This is the intelligent lookup tool — " +
|
|
1301
|
+
"give it a team name (nickname, abbreviation, or city) and it finds the matching game. " +
|
|
1302
|
+
"Returns the full prediction with game_id, confidence, odds, edge, and game time. " +
|
|
1303
|
+
"Optionally specify an opponent to find a specific matchup (e.g., team='Lakers', opponent='Celtics'). " +
|
|
1304
|
+
"The game_id in the response can be used with get_pick_card to render a shareable image.", {
|
|
1305
|
+
team: z
|
|
1306
|
+
.string()
|
|
1307
|
+
.describe("Team to look up. Accepts nickname ('Lakers'), abbreviation ('LAL'), " +
|
|
1308
|
+
"city ('Los Angeles'), or full name ('Los Angeles Lakers')."),
|
|
1309
|
+
opponent: z
|
|
1310
|
+
.string()
|
|
1311
|
+
.optional()
|
|
1312
|
+
.describe("Optional opponent to narrow to a specific matchup. " +
|
|
1313
|
+
"Example: team='Lakers', opponent='Celtics'."),
|
|
1314
|
+
sport: z
|
|
1315
|
+
.enum(["NBA", "NFL", "NHL", "MLB"])
|
|
1316
|
+
.optional()
|
|
1317
|
+
.describe("Sport hint. Auto-detected from team name if omitted."),
|
|
1318
|
+
market: z
|
|
1319
|
+
.enum(["moneyline", "spread", "totals"])
|
|
1320
|
+
.default("moneyline")
|
|
1321
|
+
.describe("Which market to look up."),
|
|
1322
|
+
}, async ({ team, opponent, sport, market }) => {
|
|
1323
|
+
try {
|
|
1324
|
+
// Resolve team
|
|
1325
|
+
const resolved = resolveTeam(team, sport || undefined);
|
|
1326
|
+
if (!resolved) {
|
|
1327
|
+
// Suggest closest matches
|
|
1328
|
+
const suggestions = searchTeams(team)
|
|
1329
|
+
.slice(0, 5)
|
|
1330
|
+
.map((t) => ` ${t.abbr} — ${t.fullName} (${t.sport})`)
|
|
1331
|
+
.join("\n");
|
|
1332
|
+
return {
|
|
1333
|
+
content: [
|
|
1334
|
+
{
|
|
1335
|
+
type: "text",
|
|
1336
|
+
text: `Could not resolve team "${team}". ` +
|
|
1337
|
+
(suggestions
|
|
1338
|
+
? `Did you mean:\n${suggestions}`
|
|
1339
|
+
: "Try using the full team name or abbreviation."),
|
|
1340
|
+
},
|
|
1341
|
+
],
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
const detectedSport = sport || resolved.sport;
|
|
1345
|
+
// Fetch predictions for this team
|
|
1346
|
+
const params = {
|
|
1347
|
+
sport: detectedSport,
|
|
1348
|
+
market,
|
|
1349
|
+
sort: "game_time",
|
|
1350
|
+
limit: "10",
|
|
1351
|
+
min_confidence: "0",
|
|
1352
|
+
team: resolved.nickname,
|
|
1353
|
+
};
|
|
1354
|
+
const data = await apiFetch({
|
|
1355
|
+
path: "/api/v1/predictions",
|
|
1356
|
+
params,
|
|
1357
|
+
});
|
|
1358
|
+
let matches = data.predictions;
|
|
1359
|
+
// Filter by opponent if specified
|
|
1360
|
+
if (opponent && matches.length > 0) {
|
|
1361
|
+
const oppResolved = resolveTeam(opponent, detectedSport);
|
|
1362
|
+
const oppName = oppResolved
|
|
1363
|
+
? oppResolved.nickname.toLowerCase()
|
|
1364
|
+
: opponent.toLowerCase();
|
|
1365
|
+
matches = matches.filter((p) => p.home_team.toLowerCase().includes(oppName) ||
|
|
1366
|
+
p.away_team.toLowerCase().includes(oppName));
|
|
1367
|
+
}
|
|
1368
|
+
if (matches.length === 0) {
|
|
1369
|
+
const oppHint = opponent ? ` vs ${opponent}` : "";
|
|
1370
|
+
return {
|
|
1371
|
+
content: [
|
|
1372
|
+
{
|
|
1373
|
+
type: "text",
|
|
1374
|
+
text: `No upcoming ${market} prediction found for ${resolved.fullName}${oppHint}. ` +
|
|
1375
|
+
`The team may not have games scheduled in the current prediction window.`,
|
|
1376
|
+
},
|
|
1377
|
+
],
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
// Return the best match (first upcoming game)
|
|
1381
|
+
const game = matches[0];
|
|
1382
|
+
const allGames = matches.length > 1
|
|
1383
|
+
? `\n\n─── Other upcoming games ───\n${matches
|
|
1384
|
+
.slice(1)
|
|
1385
|
+
.map((p) => `${p.away_team} @ ${p.home_team} | Game ID: ${p.game_id}`)
|
|
1386
|
+
.join("\n")}`
|
|
1387
|
+
: "";
|
|
1388
|
+
return {
|
|
1389
|
+
content: [
|
|
1390
|
+
{
|
|
1391
|
+
type: "text",
|
|
1392
|
+
text: `${resolved.fullName} (${resolved.abbr}) — ${detectedSport} ${market}\n` +
|
|
1393
|
+
`${"═".repeat(50)}\n\n` +
|
|
1394
|
+
formatPick(game) +
|
|
1395
|
+
allGames +
|
|
1396
|
+
formatRateLimit(data.rate_limit) +
|
|
1397
|
+
`\n\nUse game_id "${game.game_id}" with get_pick_card to render a shareable image.`,
|
|
1398
|
+
},
|
|
1399
|
+
{
|
|
1400
|
+
type: "text",
|
|
1401
|
+
text: "\n---\nRaw JSON:\n" + JSON.stringify(game, null, 2),
|
|
1402
|
+
},
|
|
1403
|
+
],
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
catch (err) {
|
|
1407
|
+
return errorResult(err);
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
// ─── Tool: lookup_teams ─────────────────────────────────────────────────────
|
|
1411
|
+
server.tool("lookup_teams", "Search the team registry by name, abbreviation, city, or sport. " +
|
|
1412
|
+
"Returns matching teams with their abbreviation, full name, sport, and aliases. " +
|
|
1413
|
+
"Useful for discovering team names before using get_game or get_predictions. " +
|
|
1414
|
+
"Omit query to list all teams for a sport. " +
|
|
1415
|
+
"Does not call the API — instant local lookup, no rate limit impact.", {
|
|
1416
|
+
query: z
|
|
1417
|
+
.string()
|
|
1418
|
+
.optional()
|
|
1419
|
+
.describe("Search query — team name, abbreviation, or city. " +
|
|
1420
|
+
"Examples: 'NY', 'Lakers', 'CHI', 'Boston'. Omit to list all teams for a sport."),
|
|
1421
|
+
sport: z
|
|
1422
|
+
.enum(["NBA", "NFL", "NHL", "MLB"])
|
|
1423
|
+
.optional()
|
|
1424
|
+
.describe("Filter to a specific sport. Omit for all sports."),
|
|
1425
|
+
}, async ({ query, sport }) => {
|
|
1426
|
+
if (!query && !sport) {
|
|
1427
|
+
// List summary
|
|
1428
|
+
const counts = { NBA: 0, NFL: 0, NHL: 0, MLB: 0 };
|
|
1429
|
+
for (const t of TEAMS)
|
|
1430
|
+
counts[t.sport]++;
|
|
1431
|
+
return {
|
|
1432
|
+
content: [
|
|
1433
|
+
{
|
|
1434
|
+
type: "text",
|
|
1435
|
+
text: `Da Vinci Bets covers ${TEAMS.length} teams:\n` +
|
|
1436
|
+
Object.entries(counts)
|
|
1437
|
+
.map(([s, n]) => ` ${s}: ${n} teams`)
|
|
1438
|
+
.join("\n") +
|
|
1439
|
+
`\n\nUse query or sport to search. Examples:\n` +
|
|
1440
|
+
` lookup_teams(query="NY") — all NY teams\n` +
|
|
1441
|
+
` lookup_teams(sport="NBA") — all NBA teams\n` +
|
|
1442
|
+
` lookup_teams(query="CHI", sport="MLB") — Chicago MLB teams`,
|
|
1443
|
+
},
|
|
1444
|
+
],
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
let results;
|
|
1448
|
+
if (query) {
|
|
1449
|
+
results = searchTeams(query, sport || undefined);
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
// List all teams for the sport
|
|
1453
|
+
results = TEAMS.filter((t) => t.sport === sport);
|
|
1454
|
+
}
|
|
1455
|
+
if (results.length === 0) {
|
|
1456
|
+
return {
|
|
1457
|
+
content: [
|
|
1458
|
+
{
|
|
1459
|
+
type: "text",
|
|
1460
|
+
text: `No teams found${query ? ` matching "${query}"` : ""}${sport ? ` in ${sport}` : ""}.`,
|
|
1461
|
+
},
|
|
1462
|
+
],
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
const lines = results.map((t) => {
|
|
1466
|
+
let line = `${t.abbr.padEnd(4)} ${t.fullName} (${t.sport})`;
|
|
1467
|
+
if (t.aliases.length > 0) {
|
|
1468
|
+
line += ` — also: ${t.aliases.join(", ")}`;
|
|
1469
|
+
}
|
|
1470
|
+
return line;
|
|
1471
|
+
});
|
|
1472
|
+
const header = query
|
|
1473
|
+
? `Found ${results.length} teams matching "${query}"${sport ? ` in ${sport}` : ""}:`
|
|
1474
|
+
: `All ${results.length} ${sport} teams:`;
|
|
1475
|
+
return {
|
|
1476
|
+
content: [
|
|
1477
|
+
{
|
|
1478
|
+
type: "text",
|
|
1479
|
+
text: header + "\n\n" + lines.join("\n"),
|
|
1480
|
+
},
|
|
1481
|
+
],
|
|
1482
|
+
};
|
|
1483
|
+
});
|
|
187
1484
|
// ─── Tool: get_top_picks ────────────────────────────────────────────────────
|
|
188
1485
|
server.tool("get_top_picks", "Get the highest-conviction AI picks across all sports and markets. " +
|
|
189
|
-
"Returns the top predictions sorted by confidence, great for daily briefings. " +
|
|
190
|
-
"
|
|
1486
|
+
"Returns the top predictions sorted by confidence with game IDs, great for daily briefings. " +
|
|
1487
|
+
"Each pick includes a game_id you can use with get_pick_card. " +
|
|
1488
|
+
"Note: fetches from 3 markets in parallel, consuming 3 API requests.", {
|
|
191
1489
|
count: z
|
|
192
1490
|
.number()
|
|
193
1491
|
.int()
|
|
@@ -204,7 +1502,6 @@ server.tool("get_top_picks", "Get the highest-conviction AI picks across all spo
|
|
|
204
1502
|
.describe("Minimum confidence percentage."),
|
|
205
1503
|
}, async ({ count, min_confidence }) => {
|
|
206
1504
|
try {
|
|
207
|
-
// Fetch top picks across all markets in parallel
|
|
208
1505
|
const markets = ["moneyline", "spread", "totals"];
|
|
209
1506
|
const results = await Promise.allSettled(markets.map((market) => apiFetch({
|
|
210
1507
|
path: "/api/v1/predictions",
|
|
@@ -228,8 +1525,7 @@ server.tool("get_top_picks", "Get the highest-conviction AI picks across all spo
|
|
|
228
1525
|
failedMarkets.push(markets[i]);
|
|
229
1526
|
}
|
|
230
1527
|
}
|
|
231
|
-
|
|
232
|
-
allPicks.sort((a, b) => Number(b.confidence || 0) - Number(a.confidence || 0));
|
|
1528
|
+
allPicks.sort((a, b) => Number(b.confidence) - Number(a.confidence));
|
|
233
1529
|
const topN = allPicks.slice(0, count);
|
|
234
1530
|
if (topN.length === 0) {
|
|
235
1531
|
return {
|
|
@@ -242,39 +1538,22 @@ server.tool("get_top_picks", "Get the highest-conviction AI picks across all spo
|
|
|
242
1538
|
};
|
|
243
1539
|
}
|
|
244
1540
|
const lines = topN.map((p, i) => {
|
|
245
|
-
|
|
246
|
-
const pick = p.predicted_winner ||
|
|
247
|
-
p.predicted_pick ||
|
|
248
|
-
p.predicted_direction ||
|
|
249
|
-
"N/A";
|
|
250
|
-
const sport = p.sport;
|
|
251
|
-
const market = p._market;
|
|
252
|
-
let line = `${i + 1}. [${sport}/${market}] ${p.away_team} @ ${p.home_team}`;
|
|
253
|
-
line += `\n Pick: ${pick} | ${conf}% confidence`;
|
|
254
|
-
if (p.home_edge)
|
|
255
|
-
line += ` | ${p.home_edge}% edge`;
|
|
256
|
-
if (p.commence_time) {
|
|
257
|
-
const time = new Date(String(p.commence_time)).toLocaleString("en-US", {
|
|
258
|
-
timeZone: "America/New_York",
|
|
259
|
-
month: "short",
|
|
260
|
-
day: "numeric",
|
|
261
|
-
hour: "numeric",
|
|
262
|
-
minute: "2-digit",
|
|
263
|
-
timeZoneName: "short",
|
|
264
|
-
});
|
|
265
|
-
line += `\n Game: ${time}`;
|
|
266
|
-
}
|
|
267
|
-
return line;
|
|
1541
|
+
return `${i + 1}. [${p.sport}/${p._market}] ${formatPick(p)}`;
|
|
268
1542
|
});
|
|
269
|
-
let header = `Top ${topN.length} AI Picks (
|
|
1543
|
+
let header = `Top ${topN.length} AI Picks (>=${min_confidence}% confidence):`;
|
|
270
1544
|
if (failedMarkets.length > 0) {
|
|
271
|
-
header += `\
|
|
1545
|
+
header += `\nWarning: Could not fetch ${failedMarkets.join(", ")} predictions.`;
|
|
272
1546
|
}
|
|
1547
|
+
// Collect game IDs for quick reference
|
|
1548
|
+
const gameIds = topN.map((p) => p.game_id);
|
|
273
1549
|
return {
|
|
274
1550
|
content: [
|
|
275
1551
|
{
|
|
276
1552
|
type: "text",
|
|
277
|
-
text: header +
|
|
1553
|
+
text: header +
|
|
1554
|
+
"\n\n" +
|
|
1555
|
+
lines.join("\n\n") +
|
|
1556
|
+
`\n\n─── Game IDs (for get_pick_card) ───\n${gameIds.join("\n")}`,
|
|
278
1557
|
},
|
|
279
1558
|
],
|
|
280
1559
|
};
|
|
@@ -284,54 +1563,138 @@ server.tool("get_top_picks", "Get the highest-conviction AI picks across all spo
|
|
|
284
1563
|
}
|
|
285
1564
|
});
|
|
286
1565
|
// ─── Tool: get_pick_card ────────────────────────────────────────────────────
|
|
287
|
-
server.tool("get_pick_card", "Render a prediction as a shareable
|
|
1566
|
+
server.tool("get_pick_card", "Render a prediction as a shareable 1080x1350 PNG image card with ESPN logos, " +
|
|
288
1567
|
"L5 form dots, confidence bar, expanded AI analysis, and injuries. " +
|
|
289
|
-
"
|
|
290
|
-
"
|
|
1568
|
+
"Returns a permanent public URL (cached in Supabase Storage) plus an inline image preview. " +
|
|
1569
|
+
"Use the URL for video editing, social media posts, or embedding anywhere. " +
|
|
1570
|
+
"Accepts a game_id from get_predictions, get_game, or get_top_picks results. " +
|
|
1571
|
+
"You can also pass a team name instead of game_id — it will resolve automatically. " +
|
|
291
1572
|
"Partner-tier keys recommended (higher rate limits for batch generation).", {
|
|
292
1573
|
game_id: z
|
|
293
1574
|
.string()
|
|
294
|
-
.
|
|
295
|
-
.
|
|
296
|
-
|
|
1575
|
+
.optional()
|
|
1576
|
+
.describe("The game_id from a prediction result. Use get_predictions or get_game to find game IDs."),
|
|
1577
|
+
team: z
|
|
1578
|
+
.string()
|
|
1579
|
+
.optional()
|
|
1580
|
+
.describe("Alternative to game_id — pass a team name and the card for their next game is rendered. " +
|
|
1581
|
+
"Examples: 'Lakers', 'NYY'. Requires an extra API call to resolve."),
|
|
297
1582
|
sport: z
|
|
298
1583
|
.enum(["NBA", "NFL", "NHL", "MLB"])
|
|
299
1584
|
.optional()
|
|
300
1585
|
.describe("Sport for the game (helps with lookup speed)."),
|
|
301
|
-
}, async ({ game_id, sport }) => {
|
|
1586
|
+
}, async ({ game_id, team, sport }) => {
|
|
302
1587
|
try {
|
|
1588
|
+
let resolvedGameId = game_id;
|
|
1589
|
+
// If team given instead of game_id, resolve it
|
|
1590
|
+
if (!resolvedGameId && team) {
|
|
1591
|
+
const resolved = resolveTeam(team, sport || undefined);
|
|
1592
|
+
const teamName = resolved ? resolved.nickname : team;
|
|
1593
|
+
const teamSport = sport || resolved?.sport;
|
|
1594
|
+
const params = {
|
|
1595
|
+
market: "moneyline",
|
|
1596
|
+
sort: "game_time",
|
|
1597
|
+
limit: "1",
|
|
1598
|
+
min_confidence: "0",
|
|
1599
|
+
team: teamName,
|
|
1600
|
+
};
|
|
1601
|
+
if (teamSport)
|
|
1602
|
+
params.sport = teamSport;
|
|
1603
|
+
const data = await apiFetch({
|
|
1604
|
+
path: "/api/v1/predictions",
|
|
1605
|
+
params,
|
|
1606
|
+
});
|
|
1607
|
+
if (data.predictions.length === 0) {
|
|
1608
|
+
return {
|
|
1609
|
+
content: [
|
|
1610
|
+
{
|
|
1611
|
+
type: "text",
|
|
1612
|
+
text: `No upcoming game found for "${team}". Cannot render card.`,
|
|
1613
|
+
},
|
|
1614
|
+
],
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
resolvedGameId = data.predictions[0].game_id;
|
|
1618
|
+
}
|
|
1619
|
+
if (!resolvedGameId) {
|
|
1620
|
+
return {
|
|
1621
|
+
content: [
|
|
1622
|
+
{
|
|
1623
|
+
type: "text",
|
|
1624
|
+
text: "Please provide either a game_id or team name.",
|
|
1625
|
+
},
|
|
1626
|
+
],
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
// Validate game_id format
|
|
1630
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(resolvedGameId)) {
|
|
1631
|
+
return {
|
|
1632
|
+
content: [
|
|
1633
|
+
{
|
|
1634
|
+
type: "text",
|
|
1635
|
+
text: "Invalid game_id format. Must be alphanumeric with _ or -.",
|
|
1636
|
+
},
|
|
1637
|
+
],
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
303
1640
|
const params = {};
|
|
304
1641
|
if (sport)
|
|
305
1642
|
params.sport = sport;
|
|
306
|
-
|
|
307
|
-
|
|
1643
|
+
// Get permanent public URL
|
|
1644
|
+
const cardData = await apiFetch({
|
|
1645
|
+
path: `/api/social/card-url/${encodeURIComponent(resolvedGameId)}`,
|
|
308
1646
|
params,
|
|
309
|
-
raw: true,
|
|
310
1647
|
});
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
1648
|
+
// Also fetch the actual image for inline preview
|
|
1649
|
+
let imageContent = null;
|
|
1650
|
+
try {
|
|
1651
|
+
const imageBuffer = await apiFetch({
|
|
1652
|
+
path: `/api/social/card-render/${encodeURIComponent(resolvedGameId)}`,
|
|
1653
|
+
params,
|
|
1654
|
+
raw: true,
|
|
1655
|
+
});
|
|
1656
|
+
imageContent = {
|
|
1657
|
+
type: "image",
|
|
1658
|
+
data: Buffer.from(imageBuffer).toString("base64"),
|
|
1659
|
+
mimeType: "image/png",
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
catch {
|
|
1663
|
+
// Image preview is optional
|
|
1664
|
+
}
|
|
1665
|
+
const content = [];
|
|
1666
|
+
if (imageContent) {
|
|
1667
|
+
content.push(imageContent);
|
|
1668
|
+
}
|
|
1669
|
+
const sizeKB = cardData.size
|
|
1670
|
+
? `${Math.round(cardData.size / 1024)}KB`
|
|
1671
|
+
: "unknown size";
|
|
1672
|
+
content.push({
|
|
1673
|
+
type: "text",
|
|
1674
|
+
text: [
|
|
1675
|
+
`Pick card for game ${resolvedGameId} (1080x1350 PNG, ${sizeKB})`,
|
|
1676
|
+
``,
|
|
1677
|
+
`Public URL: ${cardData.url}`,
|
|
1678
|
+
`Cached: ${cardData.cached}`,
|
|
1679
|
+
cardData.rendered_at ? `Rendered: ${cardData.rendered_at}` : "",
|
|
1680
|
+
``,
|
|
1681
|
+
`Use this URL in videos, social media posts, or anywhere you need the image.`,
|
|
1682
|
+
]
|
|
1683
|
+
.filter(Boolean)
|
|
1684
|
+
.join("\n"),
|
|
1685
|
+
});
|
|
1686
|
+
return { content };
|
|
325
1687
|
}
|
|
326
1688
|
catch (err) {
|
|
327
1689
|
return errorResult(err);
|
|
328
1690
|
}
|
|
329
1691
|
});
|
|
330
1692
|
// ─── Tool: get_batch_cards ──────────────────────────────────────────────────
|
|
331
|
-
server.tool("get_batch_cards", "Render multiple pick cards
|
|
332
|
-
"Designed for marketing teams generating social media content. " +
|
|
333
|
-
"
|
|
334
|
-
"
|
|
1693
|
+
server.tool("get_batch_cards", "Render multiple pick cards and return permanent public URLs for each. " +
|
|
1694
|
+
"Designed for marketing teams generating social media content and video assets. " +
|
|
1695
|
+
"Each card is cached in Supabase Storage — subsequent calls return cached URLs instantly. " +
|
|
1696
|
+
"Partner-tier API key recommended. Max 10 per call. " +
|
|
1697
|
+
"Returns URLs suitable for video editing, social posts, or any image embed.", {
|
|
335
1698
|
game_ids: z
|
|
336
1699
|
.array(z
|
|
337
1700
|
.string()
|
|
@@ -350,26 +1713,25 @@ server.tool("get_batch_cards", "Render multiple pick cards as PNG images in one
|
|
|
350
1713
|
if (sport)
|
|
351
1714
|
params.sport = sport;
|
|
352
1715
|
const results = await Promise.allSettled(game_ids.map(async (gid) => {
|
|
353
|
-
const
|
|
354
|
-
path: `/api/social/card-
|
|
1716
|
+
const data = await apiFetch({
|
|
1717
|
+
path: `/api/social/card-url/${encodeURIComponent(gid)}`,
|
|
355
1718
|
params,
|
|
356
|
-
raw: true,
|
|
357
1719
|
});
|
|
358
|
-
return
|
|
1720
|
+
return data;
|
|
359
1721
|
}));
|
|
360
1722
|
const content = [];
|
|
1723
|
+
const urls = [];
|
|
361
1724
|
let successCount = 0;
|
|
362
1725
|
for (const r of results) {
|
|
363
1726
|
if (r.status === "fulfilled") {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
});
|
|
1727
|
+
urls.push(r.value.url);
|
|
1728
|
+
const sizeKB = r.value.size
|
|
1729
|
+
? `${Math.round(r.value.size / 1024)}KB`
|
|
1730
|
+
: "";
|
|
1731
|
+
const cached = r.value.cached ? " (cached)" : " (fresh render)";
|
|
370
1732
|
content.push({
|
|
371
1733
|
type: "text",
|
|
372
|
-
text:
|
|
1734
|
+
text: `${r.value.game_id}: ${r.value.url}${sizeKB ? ` — ${sizeKB}` : ""}${cached}`,
|
|
373
1735
|
});
|
|
374
1736
|
successCount++;
|
|
375
1737
|
}
|
|
@@ -382,8 +1744,14 @@ server.tool("get_batch_cards", "Render multiple pick cards as PNG images in one
|
|
|
382
1744
|
}
|
|
383
1745
|
content.unshift({
|
|
384
1746
|
type: "text",
|
|
385
|
-
text: `Rendered ${successCount}/${game_ids.length} pick cards
|
|
1747
|
+
text: `Rendered ${successCount}/${game_ids.length} pick cards.\n\nPublic URLs:`,
|
|
386
1748
|
});
|
|
1749
|
+
if (urls.length > 0) {
|
|
1750
|
+
content.push({
|
|
1751
|
+
type: "text",
|
|
1752
|
+
text: `\n---\nAll URLs:\n${urls.join("\n")}`,
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
387
1755
|
return { content };
|
|
388
1756
|
}
|
|
389
1757
|
catch (err) {
|
|
@@ -392,20 +1760,17 @@ server.tool("get_batch_cards", "Render multiple pick cards as PNG images in one
|
|
|
392
1760
|
});
|
|
393
1761
|
// ─── Tool: get_performance ──────────────────────────────────────────────────
|
|
394
1762
|
server.tool("get_performance", "Get a snapshot of current active predictions across sports and markets. " +
|
|
395
|
-
"Shows prediction counts, confidence distribution, and available coverage.
|
|
396
|
-
"For historical win rates, visit davincibets.io/analytics.", {
|
|
1763
|
+
"Shows prediction counts, confidence distribution, and available coverage.", {
|
|
397
1764
|
sport: z
|
|
398
1765
|
.enum(["NBA", "NFL", "NHL", "MLB"])
|
|
399
1766
|
.optional()
|
|
400
|
-
.describe("Filter
|
|
1767
|
+
.describe("Filter to a specific sport."),
|
|
401
1768
|
market: z
|
|
402
1769
|
.enum(["moneyline", "spread", "totals"])
|
|
403
1770
|
.optional()
|
|
404
1771
|
.describe("Filter to a specific market."),
|
|
405
1772
|
}, async ({ sport, market }) => {
|
|
406
1773
|
try {
|
|
407
|
-
// Pull recent settled predictions to compute win rates
|
|
408
|
-
// We use the predictions endpoint with high confidence to show quality
|
|
409
1774
|
const params = {
|
|
410
1775
|
sort: "confidence",
|
|
411
1776
|
limit: "50",
|
|
@@ -419,7 +1784,6 @@ server.tool("get_performance", "Get a snapshot of current active predictions acr
|
|
|
419
1784
|
path: "/api/v1/predictions",
|
|
420
1785
|
params,
|
|
421
1786
|
});
|
|
422
|
-
// Summarize available predictions as a performance snapshot
|
|
423
1787
|
const byMarket = {};
|
|
424
1788
|
const bySport = {};
|
|
425
1789
|
const confBuckets = { high: 0, medium: 0 };
|
|
@@ -428,15 +1792,14 @@ server.tool("get_performance", "Get a snapshot of current active predictions acr
|
|
|
428
1792
|
const s = String(p.sport || "?");
|
|
429
1793
|
byMarket[m] = (byMarket[m] || 0) + 1;
|
|
430
1794
|
bySport[s] = (bySport[s] || 0) + 1;
|
|
431
|
-
|
|
432
|
-
if (conf >= 65)
|
|
1795
|
+
if (p.confidence >= 65)
|
|
433
1796
|
confBuckets.high++;
|
|
434
1797
|
else
|
|
435
1798
|
confBuckets.medium++;
|
|
436
1799
|
}
|
|
437
1800
|
const lines = [
|
|
438
1801
|
"Da Vinci Bets — Current Predictions Snapshot",
|
|
439
|
-
"
|
|
1802
|
+
"═".repeat(50),
|
|
440
1803
|
`Total active predictions: ${data.meta?.total ?? data.predictions.length}`,
|
|
441
1804
|
"",
|
|
442
1805
|
"By Sport:",
|
|
@@ -446,13 +1809,9 @@ server.tool("get_performance", "Get a snapshot of current active predictions acr
|
|
|
446
1809
|
...Object.entries(byMarket).map(([m, n]) => ` ${m}: ${n} picks`),
|
|
447
1810
|
"",
|
|
448
1811
|
"Confidence Distribution:",
|
|
449
|
-
` High (
|
|
1812
|
+
` High (>=65%): ${confBuckets.high} picks`,
|
|
450
1813
|
` Medium (50-64%): ${confBuckets.medium} picks`,
|
|
451
|
-
|
|
452
|
-
`Rate limit: ${data.rate_limit?.remaining_hour ?? "?"}/hour, ${data.rate_limit?.remaining_day ?? "?"}/day remaining.`,
|
|
453
|
-
"",
|
|
454
|
-
"Note: Historical win rates are available on the Da Vinci Bets analytics dashboard.",
|
|
455
|
-
"Visit https://www.davincibets.io/analytics for full performance tracking.",
|
|
1814
|
+
formatRateLimit(data.rate_limit),
|
|
456
1815
|
];
|
|
457
1816
|
return {
|
|
458
1817
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
@@ -462,9 +1821,86 @@ server.tool("get_performance", "Get a snapshot of current active predictions acr
|
|
|
462
1821
|
return errorResult(err);
|
|
463
1822
|
}
|
|
464
1823
|
});
|
|
1824
|
+
// ─── Tool: get_track_record ─────────────────────────────────────────────────
|
|
1825
|
+
server.tool("get_track_record", "Get verified win rates, ROI, and historical performance from settled predictions. " +
|
|
1826
|
+
"Shows actual results broken down by sport, market, and confidence tier. " +
|
|
1827
|
+
"Includes current streak and recent settled picks. " +
|
|
1828
|
+
"Public endpoint — does not count against your API rate limit.", {
|
|
1829
|
+
sport: z
|
|
1830
|
+
.enum(["NBA", "NFL", "NHL", "MLB"])
|
|
1831
|
+
.optional()
|
|
1832
|
+
.describe("Filter to a specific sport. Omit for all."),
|
|
1833
|
+
days: z
|
|
1834
|
+
.number()
|
|
1835
|
+
.int()
|
|
1836
|
+
.min(7)
|
|
1837
|
+
.max(365)
|
|
1838
|
+
.default(30)
|
|
1839
|
+
.describe("Lookback period in days (7-365)."),
|
|
1840
|
+
}, async ({ sport, days }) => {
|
|
1841
|
+
try {
|
|
1842
|
+
const params = { days: String(days) };
|
|
1843
|
+
if (sport)
|
|
1844
|
+
params.sport = sport;
|
|
1845
|
+
const url = new URL("/api/track-record", API_BASE);
|
|
1846
|
+
for (const [k, v] of Object.entries(params)) {
|
|
1847
|
+
url.searchParams.set(k, v);
|
|
1848
|
+
}
|
|
1849
|
+
const res = await fetch(url.toString(), {
|
|
1850
|
+
headers: { Accept: "application/json" },
|
|
1851
|
+
signal: AbortSignal.timeout(15_000),
|
|
1852
|
+
});
|
|
1853
|
+
if (!res.ok) {
|
|
1854
|
+
throw new Error(`Track record request failed (HTTP ${res.status}).`);
|
|
1855
|
+
}
|
|
1856
|
+
const data = (await res.json());
|
|
1857
|
+
const breakdownLines = data.breakdown.map((b) => {
|
|
1858
|
+
const emoji = b.win_rate >= 55 ? "+" : b.win_rate >= 50 ? "~" : "-";
|
|
1859
|
+
return ` ${b.sport} ${b.market_type}: ${b.wins}-${b.losses}${b.pushes ? `-${b.pushes}P` : ""} (${b.win_rate}% WR, ${emoji}${b.roi}% ROI)`;
|
|
1860
|
+
});
|
|
1861
|
+
const tierLines = data.confidenceTiers.map((t) => ` ${t.tier}%: ${t.wins}/${t.total} (${t.win_rate}% WR)`);
|
|
1862
|
+
const recentLines = data.recentPicks.slice(0, 10).map((p) => {
|
|
1863
|
+
const icon = p.result === "win" ? "W" : p.result === "loss" ? "L" : "P";
|
|
1864
|
+
return ` [${icon}] ${p.sport} ${p.market_type}: ${p.pick_team}`;
|
|
1865
|
+
});
|
|
1866
|
+
const lines = [
|
|
1867
|
+
`Da Vinci Bets — Track Record (${data.period})`,
|
|
1868
|
+
"═".repeat(50),
|
|
1869
|
+
data.streak ? `Current Streak: ${data.streak}` : "",
|
|
1870
|
+
"",
|
|
1871
|
+
"Performance by Sport & Market:",
|
|
1872
|
+
...breakdownLines,
|
|
1873
|
+
"",
|
|
1874
|
+
"By Confidence Tier:",
|
|
1875
|
+
...tierLines,
|
|
1876
|
+
"",
|
|
1877
|
+
`Recent Settled Picks (last ${Math.min(data.recentPicks.length, 10)}):`,
|
|
1878
|
+
...recentLines,
|
|
1879
|
+
].filter(Boolean);
|
|
1880
|
+
return {
|
|
1881
|
+
content: [
|
|
1882
|
+
{ type: "text", text: lines.join("\n") },
|
|
1883
|
+
{
|
|
1884
|
+
type: "text",
|
|
1885
|
+
text: "\n---\nRaw JSON:\n" +
|
|
1886
|
+
JSON.stringify({
|
|
1887
|
+
period: data.period,
|
|
1888
|
+
streak: data.streak,
|
|
1889
|
+
breakdown: data.breakdown,
|
|
1890
|
+
confidenceTiers: data.confidenceTiers,
|
|
1891
|
+
}, null, 2),
|
|
1892
|
+
},
|
|
1893
|
+
],
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
catch (err) {
|
|
1897
|
+
return errorResult(err);
|
|
1898
|
+
}
|
|
1899
|
+
});
|
|
465
1900
|
// ─── Tool: get_schedule ─────────────────────────────────────────────────────
|
|
466
1901
|
server.tool("get_schedule", "Get today's game schedule with predictions summary. " +
|
|
467
|
-
"Shows which games have predictions available
|
|
1902
|
+
"Shows which games have predictions available, their confidence levels, and game IDs. " +
|
|
1903
|
+
"Game IDs can be used with get_pick_card or get_game for more details.", {
|
|
468
1904
|
sport: z
|
|
469
1905
|
.enum(["NBA", "NFL", "NHL", "MLB"])
|
|
470
1906
|
.describe("Which sport's schedule to retrieve."),
|
|
@@ -486,7 +1922,7 @@ server.tool("get_schedule", "Get today's game schedule with predictions summary.
|
|
|
486
1922
|
content: [
|
|
487
1923
|
{
|
|
488
1924
|
type: "text",
|
|
489
|
-
text: `No ${sport} games found in the current prediction window. The ${sport} season may be in offseason, or no games are scheduled
|
|
1925
|
+
text: `No ${sport} games found in the current prediction window. The ${sport} season may be in offseason, or no games are scheduled.`,
|
|
490
1926
|
},
|
|
491
1927
|
],
|
|
492
1928
|
};
|
|
@@ -498,7 +1934,7 @@ server.tool("get_schedule", "Get today's game schedule with predictions summary.
|
|
|
498
1934
|
];
|
|
499
1935
|
for (const p of data.predictions) {
|
|
500
1936
|
const time = p.commence_time
|
|
501
|
-
? new Date(
|
|
1937
|
+
? new Date(p.commence_time).toLocaleString("en-US", {
|
|
502
1938
|
timeZone: "America/New_York",
|
|
503
1939
|
weekday: "short",
|
|
504
1940
|
month: "short",
|
|
@@ -508,12 +1944,14 @@ server.tool("get_schedule", "Get today's game schedule with predictions summary.
|
|
|
508
1944
|
timeZoneName: "short",
|
|
509
1945
|
})
|
|
510
1946
|
: "TBD";
|
|
1947
|
+
const tier = p.confidence_tier ? ` [${p.confidence_tier}]` : "";
|
|
511
1948
|
lines.push(`${time}`);
|
|
512
1949
|
lines.push(` ${p.away_team} @ ${p.home_team}`);
|
|
513
|
-
lines.push(` Pick: ${p.predicted_winner} (${p.confidence}% conf)
|
|
1950
|
+
lines.push(` Pick: ${p.predicted_winner} (${p.confidence}% conf)${tier}`);
|
|
1951
|
+
lines.push(` Game ID: ${p.game_id}`);
|
|
514
1952
|
lines.push("");
|
|
515
1953
|
}
|
|
516
|
-
lines.push("Use
|
|
1954
|
+
lines.push("Use game IDs with get_pick_card to render shareable images, or get_game for full details.");
|
|
517
1955
|
return {
|
|
518
1956
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
519
1957
|
};
|
|
@@ -534,9 +1972,10 @@ function errorResult(err) {
|
|
|
534
1972
|
async function main() {
|
|
535
1973
|
const transport = new StdioServerTransport();
|
|
536
1974
|
await server.connect(transport);
|
|
537
|
-
console.error("Da Vinci Bets MCP Server v1.
|
|
1975
|
+
console.error("Da Vinci Bets MCP Server v1.3.1 — connected via stdio");
|
|
538
1976
|
console.error(`API: ${API_BASE}`);
|
|
539
1977
|
console.error(`Key: dvb_live_***`);
|
|
1978
|
+
console.error(`Teams: ${TEAMS.length} teams loaded across 4 sports`);
|
|
540
1979
|
}
|
|
541
1980
|
main().catch((err) => {
|
|
542
1981
|
console.error("Fatal:", err);
|