odds-api-mcp-server 1.1.1 → 1.2.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 CHANGED
@@ -160,7 +160,7 @@ Once configured, ask your AI assistant things like:
160
160
 
161
161
  - "What sports are available on Odds-API?"
162
162
  - "Show me upcoming Premier League matches"
163
- - "Get odds for the next Arsenal match from Bet365 and Pinnacle"
163
+ - "Get odds for the next Arsenal match from Bet365 and SingBet"
164
164
  - "Find value bets on Bet365 with event details"
165
165
  - "Are there any arbitrage opportunities between Bet365 and Unibet?"
166
166
  - "Show me how the odds moved for event 12345 on the spread market"
@@ -186,3 +186,5 @@ MIT
186
186
  - [Documentation](https://docs.odds-api.io) - API docs
187
187
  - [API Reference](https://api.odds-api.io/v3/docs/index.html) - Swagger/OpenAPI
188
188
  - [npm](https://www.npmjs.com/package/odds-api-mcp-server) - Package registry
189
+ ndex.html) - Swagger/OpenAPI
190
+ - [npm](https://www.npmjs.com/package/odds-api-mcp-server) - Package registry
package/dist/index.js CHANGED
@@ -319,6 +319,62 @@ const tools = [
319
319
  return jsonResponse(await apiRequest("/odds/updated", { since, bookmaker, sport }));
320
320
  },
321
321
  },
322
+ // ── Dropping Odds ───────────────────────────────────────────────
323
+ {
324
+ name: "get_dropping_odds",
325
+ description: "Get odds that have dropped the most from opening, based on sharp bookmaker data. Useful for tracking where sharp money is moving. Updated every ~10 seconds. Only available on paid plans.",
326
+ inputSchema: {
327
+ type: "object",
328
+ properties: {
329
+ sport: {
330
+ type: "string",
331
+ description: "Sport slug to filter by (e.g., 'football', 'basketball')",
332
+ },
333
+ league: {
334
+ type: "string",
335
+ description: "League slug to filter by (e.g., 'england-premier-league'). Requires sport to also be set.",
336
+ },
337
+ market: {
338
+ type: "string",
339
+ description: "Market type to filter by: 'ML', 'Spread', or 'Totals'",
340
+ },
341
+ timeWindow: {
342
+ type: "string",
343
+ description: "Time window for drop calculation and sorting: 'opening', '12h', '24h', '48h' (default: 'opening')",
344
+ },
345
+ minDrop: {
346
+ type: "number",
347
+ description: "Minimum drop percentage threshold (default: 0)",
348
+ },
349
+ limit: {
350
+ type: "number",
351
+ description: "Results per page, 1-200 (default: 50)",
352
+ },
353
+ page: {
354
+ type: "number",
355
+ description: "Page number, 1-indexed (default: 1)",
356
+ },
357
+ includeEventDetails: {
358
+ type: "boolean",
359
+ description: "Include expanded event details (home, away, date, sport, league) in response",
360
+ },
361
+ },
362
+ required: [],
363
+ },
364
+ async handler(args) {
365
+ const { sport, league, market, timeWindow, minDrop, limit, page, includeEventDetails } = args;
366
+ return jsonResponse(await apiRequest("/dropping-odds", {
367
+ sport,
368
+ league,
369
+ market,
370
+ timeWindow,
371
+ minDrop,
372
+ limit,
373
+ page,
374
+ includeEventDetails: includeEventDetails || undefined,
375
+ }));
376
+ },
377
+ },
322
378
  // ── Historical ──────────────────────────────────────────────────
323
379
  {
324
380
  name: "get_historical_events",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "odds-api-mcp-server",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "MCP server for Odds-API.io - Access sports betting odds data from AI tools like Claude, Cursor, and VS Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.test.ts CHANGED
@@ -63,6 +63,7 @@ describe("Tool Registry", () => {
63
63
  "get_multi_odds",
64
64
  "get_odds_movements",
65
65
  "get_updated_odds",
66
+ "get_dropping_odds",
66
67
  "get_historical_events",
67
68
  "get_historical_odds",
68
69
  "get_value_bets",
@@ -72,8 +73,8 @@ describe("Tool Registry", () => {
72
73
  "get_documentation",
73
74
  ];
74
75
 
75
- it("has all 21 tools registered", () => {
76
- expect(tools).toHaveLength(21);
76
+ it("has all 22 tools registered", () => {
77
+ expect(tools).toHaveLength(22);
77
78
  });
78
79
 
79
80
  it("has no duplicate tool names", () => {
@@ -134,6 +135,7 @@ describe("Tool Schemas - Required Parameters", () => {
134
135
  ["get_multi_odds", ["eventIds", "bookmakers"]],
135
136
  ["get_odds_movements", ["eventId", "bookmaker", "market"]],
136
137
  ["get_updated_odds", ["since", "bookmaker", "sport"]],
138
+ ["get_dropping_odds", []],
137
139
  ["get_historical_events", ["sport", "league", "from", "to"]],
138
140
  ["get_historical_odds", ["eventId", "bookmakers"]],
139
141
  ["get_value_bets", ["bookmaker"]],
@@ -459,6 +461,58 @@ describe("Tool Handlers", () => {
459
461
  expect(calledUrl.searchParams.get("since")).toBe(String(since));
460
462
  });
461
463
 
464
+ it("get_dropping_odds passes all optional params", async () => {
465
+ const fetchMock = mockFetchJson([]);
466
+ globalThis.fetch = fetchMock;
467
+
468
+ await toolMap.get("get_dropping_odds")!.handler({
469
+ sport: "football",
470
+ league: "england-premier-league",
471
+ market: "ML",
472
+ timeWindow: "12h",
473
+ minDrop: 5,
474
+ limit: 100,
475
+ page: 2,
476
+ includeEventDetails: true,
477
+ });
478
+
479
+ const calledUrl = new URL(fetchMock.mock.calls[0][0]);
480
+ expect(calledUrl.pathname).toBe("/v3/dropping-odds");
481
+ expect(calledUrl.searchParams.get("sport")).toBe("football");
482
+ expect(calledUrl.searchParams.get("league")).toBe("england-premier-league");
483
+ expect(calledUrl.searchParams.get("market")).toBe("ML");
484
+ expect(calledUrl.searchParams.get("timeWindow")).toBe("12h");
485
+ expect(calledUrl.searchParams.get("minDrop")).toBe("5");
486
+ expect(calledUrl.searchParams.get("limit")).toBe("100");
487
+ expect(calledUrl.searchParams.get("page")).toBe("2");
488
+ expect(calledUrl.searchParams.get("includeEventDetails")).toBe("true");
489
+ });
490
+
491
+ it("get_dropping_odds sends includeEventDetails only when true", async () => {
492
+ const fetchMock = mockFetchJson([]);
493
+ globalThis.fetch = fetchMock;
494
+
495
+ await toolMap.get("get_dropping_odds")!.handler({ includeEventDetails: false });
496
+ const url = new URL(fetchMock.mock.calls[0][0]);
497
+ expect(url.searchParams.has("includeEventDetails")).toBe(false);
498
+
499
+ await toolMap.get("get_dropping_odds")!.handler({});
500
+ const url2 = new URL(fetchMock.mock.calls[1][0]);
501
+ expect(url2.searchParams.has("includeEventDetails")).toBe(false);
502
+ });
503
+
504
+ it("get_dropping_odds works with no params", async () => {
505
+ const fetchMock = mockFetchJson([]);
506
+ globalThis.fetch = fetchMock;
507
+
508
+ await toolMap.get("get_dropping_odds")!.handler({});
509
+
510
+ const calledUrl = new URL(fetchMock.mock.calls[0][0]);
511
+ expect(calledUrl.pathname).toBe("/v3/dropping-odds");
512
+ expect(calledUrl.searchParams.has("sport")).toBe(false);
513
+ expect(calledUrl.searchParams.has("league")).toBe(false);
514
+ });
515
+
462
516
  it("get_historical_events passes all required params", async () => {
463
517
  const fetchMock = mockFetchJson([]);
464
518
  globalThis.fetch = fetchMock;
package/src/index.ts CHANGED
@@ -408,6 +408,77 @@ const tools: ToolDefinition[] = [
408
408
  },
409
409
  },
410
410
 
411
+ // ── Dropping Odds ───────────────────────────────────────────────
412
+
413
+ {
414
+ name: "get_dropping_odds",
415
+ description:
416
+ "Get odds that have dropped the most from opening, based on sharp bookmaker data. Useful for tracking where sharp money is moving. Updated every ~10 seconds. Only available on paid plans.",
417
+ inputSchema: {
418
+ type: "object",
419
+ properties: {
420
+ sport: {
421
+ type: "string",
422
+ description: "Sport slug to filter by (e.g., 'football', 'basketball')",
423
+ },
424
+ league: {
425
+ type: "string",
426
+ description: "League slug to filter by (e.g., 'england-premier-league'). Requires sport to also be set.",
427
+ },
428
+ market: {
429
+ type: "string",
430
+ description: "Market type to filter by: 'ML', 'Spread', or 'Totals'",
431
+ },
432
+ timeWindow: {
433
+ type: "string",
434
+ description: "Time window for drop calculation and sorting: 'opening', '12h', '24h', '48h' (default: 'opening')",
435
+ },
436
+ minDrop: {
437
+ type: "number",
438
+ description: "Minimum drop percentage threshold (default: 0)",
439
+ },
440
+ limit: {
441
+ type: "number",
442
+ description: "Results per page, 1-200 (default: 50)",
443
+ },
444
+ page: {
445
+ type: "number",
446
+ description: "Page number, 1-indexed (default: 1)",
447
+ },
448
+ includeEventDetails: {
449
+ type: "boolean",
450
+ description: "Include expanded event details (home, away, date, sport, league) in response",
451
+ },
452
+ },
453
+ required: [],
454
+ },
455
+ async handler(args) {
456
+ const { sport, league, market, timeWindow, minDrop, limit, page, includeEventDetails } =
457
+ args as {
458
+ sport?: string;
459
+ league?: string;
460
+ market?: string;
461
+ timeWindow?: string;
462
+ minDrop?: number;
463
+ limit?: number;
464
+ page?: number;
465
+ includeEventDetails?: boolean;
466
+ };
467
+ return jsonResponse(
468
+ await apiRequest("/dropping-odds", {
469
+ sport,
470
+ league,
471
+ market,
472
+ timeWindow,
473
+ minDrop,
474
+ limit,
475
+ page,
476
+ includeEventDetails: includeEventDetails || undefined,
477
+ }),
478
+ );
479
+ },
480
+ },
481
+
411
482
  // ── Historical ──────────────────────────────────────────────────
412
483
 
413
484
  {