odds-api-mcp-server 1.1.0 → 1.2.0

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
@@ -4,11 +4,11 @@ Model Context Protocol (MCP) server for [Odds-API.io](https://odds-api.io) - pro
4
4
 
5
5
  ## Features
6
6
 
7
- - **12 API tools** for fetching sports, bookmakers, events, odds, value bets, and arbitrage opportunities
7
+ - **21 API tools** covering the full Odds-API.io v3 surface: sports, events, odds, historical data, value bets, arbitrage, and more
8
8
  - **Documentation resources** for AI context
9
- - **Real-time data** from 265 bookmakers across 34 sports
9
+ - **Real-time data** from 265+ bookmakers across 34 sports
10
10
 
11
- ## Installation
11
+ ## Quick Start
12
12
 
13
13
  ### Claude Code CLI
14
14
 
@@ -16,32 +16,27 @@ Model Context Protocol (MCP) server for [Odds-API.io](https://odds-api.io) - pro
16
16
  claude mcp add odds-api --env ODDS_API_KEY="your-api-key" -- npx -y odds-api-mcp-server
17
17
  ```
18
18
 
19
- ### Manual Installation
20
-
21
- ```bash
22
- npm install -g odds-api-mcp-server
23
- ```
19
+ ### Claude Desktop
24
20
 
25
- ### From Source
21
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
26
22
 
27
- ```bash
28
- git clone https://github.com/odds-api-io/odds-api-mcp-server
29
- cd odds-api-mcp-server
30
- npm install
31
- npm run build
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "odds-api": {
27
+ "command": "npx",
28
+ "args": ["-y", "odds-api-mcp-server"],
29
+ "env": {
30
+ "ODDS_API_KEY": "your-api-key"
31
+ }
32
+ }
33
+ }
34
+ }
32
35
  ```
33
36
 
34
- ## Configuration
35
-
36
- ### Environment Variables
37
-
38
- - `ODDS_API_KEY` (required): Your Odds-API.io API key
39
-
40
- Get your API key at [odds-api.io](https://odds-api.io).
41
-
42
- ### Claude Desktop
37
+ ### Cursor
43
38
 
44
- Add to your Claude Desktop config (`~/.config/claude/claude_desktop_config.json` on macOS/Linux or `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
39
+ Add to your Cursor MCP settings (`.cursor/mcp.json` in your project or global config):
45
40
 
46
41
  ```json
47
42
  {
@@ -57,67 +52,128 @@ Add to your Claude Desktop config (`~/.config/claude/claude_desktop_config.json`
57
52
  }
58
53
  ```
59
54
 
60
- ### Cursor
55
+ ### VS Code
61
56
 
62
- Add to your Cursor MCP settings:
57
+ Add to your VS Code settings (`.vscode/mcp.json`):
63
58
 
64
59
  ```json
65
60
  {
66
- "odds-api": {
67
- "command": "npx",
68
- "args": ["-y", "odds-api-mcp-server"],
69
- "env": {
70
- "ODDS_API_KEY": "your-api-key"
61
+ "servers": {
62
+ "odds-api": {
63
+ "command": "npx",
64
+ "args": ["-y", "odds-api-mcp-server"],
65
+ "env": {
66
+ "ODDS_API_KEY": "your-api-key"
67
+ }
71
68
  }
72
69
  }
73
70
  }
74
71
  ```
75
72
 
73
+ ### Global Install (alternative)
74
+
75
+ ```bash
76
+ npm install -g odds-api-mcp-server
77
+ ```
78
+
79
+ Then use `odds-api-mcp` as the command instead of `npx -y odds-api-mcp-server`.
80
+
81
+ ## Configuration
82
+
83
+ | Variable | Required | Description |
84
+ |----------|----------|-------------|
85
+ | `ODDS_API_KEY` | Yes | Your API key from [odds-api.io](https://odds-api.io) |
86
+
76
87
  ## Available Tools
77
88
 
89
+ ### Sports & Leagues
90
+
91
+ | Tool | Description |
92
+ |------|-------------|
93
+ | `get_sports` | List all available sports with slugs |
94
+ | `get_leagues` | Get leagues for a sport (with optional `all` flag for inactive leagues) |
95
+
96
+ ### Bookmakers
97
+
98
+ | Tool | Description |
99
+ |------|-------------|
100
+ | `get_bookmakers` | List all supported bookmakers |
101
+ | `get_selected_bookmakers` | Get your currently selected bookmakers |
102
+ | `select_bookmakers` | Add bookmakers to your selection |
103
+ | `clear_selected_bookmakers` | Clear all selected bookmakers (once per 12h) |
104
+
105
+ ### Events
106
+
78
107
  | Tool | Description |
79
108
  |------|-------------|
80
- | `get_sports` | List all available sports |
81
- | `get_bookmakers` | List all available bookmakers |
82
- | `get_leagues` | Get leagues for a sport |
83
- | `get_events` | Get events with filtering options |
109
+ | `get_events` | Get events with filtering (league, status, date range, participant, bookmaker, pagination) |
110
+ | `get_event` | Get a single event by ID |
84
111
  | `get_live_events` | Get currently live events |
85
- | `search_events` | Search events by text |
86
- | `get_odds` | Get odds for an event |
87
- | `get_multi_odds` | Get odds for multiple events (batch) |
88
- | `get_value_bets` | Get value betting opportunities |
89
- | `get_arbitrage_bets` | Get arbitrage opportunities |
90
- | `get_participants` | Get teams/participants |
91
- | `get_documentation` | Get API documentation |
112
+ | `search_events` | Search events by team name or text |
92
113
 
93
- ## Available Resources
114
+ ### Odds
94
115
 
95
- | Resource | Description |
96
- |----------|-------------|
97
- | `odds-api://documentation` | Full API documentation |
98
- | `odds-api://openapi` | OpenAPI specification |
116
+ | Tool | Description |
117
+ |------|-------------|
118
+ | `get_odds` | Get odds for an event from selected bookmakers |
119
+ | `get_multi_odds` | Get odds for up to 10 events in one call |
120
+ | `get_odds_movements` | Get historical line movements for a market |
121
+ | `get_updated_odds` | Get recently changed odds (polling) |
122
+
123
+ ### Historical
124
+
125
+ | Tool | Description |
126
+ |------|-------------|
127
+ | `get_historical_events` | Get finished events for a sport/league/date range |
128
+ | `get_historical_odds` | Get closing odds and scores for finished events |
129
+
130
+ ### Betting Analytics
131
+
132
+ | Tool | Description |
133
+ |------|-------------|
134
+ | `get_value_bets` | Get positive EV opportunities for a bookmaker |
135
+ | `get_arbitrage_bets` | Get arbitrage opportunities with optimal stakes |
136
+
137
+ ### Participants
138
+
139
+ | Tool | Description |
140
+ |------|-------------|
141
+ | `get_participants` | Get teams/participants for a sport |
142
+ | `get_participant` | Get a single participant by ID |
143
+
144
+ ### Reference
145
+
146
+ | Tool | Description |
147
+ |------|-------------|
148
+ | `get_documentation` | Fetch full API documentation |
149
+
150
+ ## Resources
151
+
152
+ | Resource URI | Description |
153
+ |-------------|-------------|
154
+ | `odds-api://documentation` | Complete API documentation |
155
+ | `odds-api://openapi` | OpenAPI/Swagger specification |
99
156
 
100
157
  ## Example Usage
101
158
 
102
- Once configured, you can ask your AI assistant:
159
+ Once configured, ask your AI assistant things like:
103
160
 
104
- - "Get me the list of available sports"
161
+ - "What sports are available on Odds-API?"
105
162
  - "Show me upcoming Premier League matches"
106
- - "Find value bets for Bet365 with expected value above 3%"
107
- - "Search for events involving Liverpool"
108
- - "Get odds for event 123456 from Pinnacle and Bet365"
163
+ - "Get odds for the next Arsenal match from Bet365 and Pinnacle"
164
+ - "Find value bets on Bet365 with event details"
165
+ - "Are there any arbitrage opportunities between Bet365 and Unibet?"
166
+ - "Show me how the odds moved for event 12345 on the spread market"
167
+ - "Get historical results for La Liga in January 2026"
109
168
 
110
169
  ## Development
111
170
 
112
171
  ```bash
113
- # Install dependencies
172
+ git clone https://github.com/odds-api-io/odds-api-mcp-server
173
+ cd odds-api-mcp-server
114
174
  npm install
115
-
116
- # Build
117
175
  npm run build
118
-
119
- # Run in development mode
120
- npm run dev
176
+ npm test
121
177
  ```
122
178
 
123
179
  ## License
@@ -127,5 +183,6 @@ MIT
127
183
  ## Links
128
184
 
129
185
  - [Odds-API.io](https://odds-api.io) - Main website
130
- - [Documentation](https://docs.odds-api.io) - API documentation
131
- - [Support](mailto:hello@odds-api.io) - Email support
186
+ - [Documentation](https://docs.odds-api.io) - API docs
187
+ - [API Reference](https://api.odds-api.io/v3/docs/index.html) - Swagger/OpenAPI
188
+ - [npm](https://www.npmjs.com/package/odds-api-mcp-server) - Package registry
package/dist/index.js CHANGED
@@ -1,14 +1,7 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.toolMap = exports.tools = void 0;
5
- exports.apiRequest = apiRequest;
6
- exports.jsonResponse = jsonResponse;
7
- exports.textResponse = textResponse;
8
- exports.errorResponse = errorResponse;
9
- const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
10
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
11
- const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
12
5
  // ── Constants ────────────────────────────────────────────────────────────────
13
6
  const API_BASE_URL = "https://api2.odds-api.io/v3";
14
7
  const DOCS_BASE_URL = "https://docs.odds-api.io";
@@ -326,6 +319,62 @@ const tools = [
326
319
  return jsonResponse(await apiRequest("/odds/updated", { since, bookmaker, sport }));
327
320
  },
328
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
+ },
329
378
  // ── Historical ──────────────────────────────────────────────────
330
379
  {
331
380
  name: "get_historical_events",
@@ -488,19 +537,17 @@ const tools = [
488
537
  },
489
538
  },
490
539
  ];
491
- exports.tools = tools;
492
540
  // ── Server Setup ─────────────────────────────────────────────────────────────
493
541
  const toolMap = new Map(tools.map((tool) => [tool.name, tool]));
494
- exports.toolMap = toolMap;
495
- const server = new index_js_1.Server({ name: "odds-api-mcp", version: "1.1.0" }, { capabilities: { tools: {}, resources: {} } });
496
- server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
542
+ const server = new Server({ name: "odds-api-mcp", version: "1.1.1" }, { capabilities: { tools: {}, resources: {} } });
543
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
497
544
  tools: tools.map(({ name, description, inputSchema }) => ({
498
545
  name,
499
546
  description,
500
547
  inputSchema,
501
548
  })),
502
549
  }));
503
- server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
550
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
504
551
  const { name, arguments: args } = request.params;
505
552
  const tool = toolMap.get(name);
506
553
  if (!tool) {
@@ -515,7 +562,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
515
562
  }
516
563
  });
517
564
  // ── Resources ────────────────────────────────────────────────────────────────
518
- server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => ({
565
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
519
566
  resources: [
520
567
  {
521
568
  uri: "odds-api://documentation",
@@ -531,7 +578,7 @@ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => ({
531
578
  },
532
579
  ],
533
580
  }));
534
- server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
581
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
535
582
  const { uri } = request.params;
536
583
  switch (uri) {
537
584
  case "odds-api://documentation": {
@@ -566,7 +613,7 @@ async function main() {
566
613
  console.error("Error: ODDS_API_KEY environment variable is required");
567
614
  process.exit(1);
568
615
  }
569
- const transport = new stdio_js_1.StdioServerTransport();
616
+ const transport = new StdioServerTransport();
570
617
  await server.connect(transport);
571
618
  console.error("Odds-API.io MCP Server running on stdio");
572
619
  }
@@ -574,3 +621,5 @@ main().catch((error) => {
574
621
  console.error("Fatal error:", error);
575
622
  process.exit(1);
576
623
  });
624
+ // ── Exports (for testing) ────────────────────────────────────────────────────
625
+ export { tools, toolMap, apiRequest, jsonResponse, textResponse, errorResponse };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "odds-api-mcp-server",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for Odds-API.io - Access sports betting odds data from AI tools like Claude, Cursor, and VS Code",
5
+ "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "bin": {
7
- "odds-api-mcp": "dist/index.js"
8
+ "odds-api-mcp-server": "dist/index.js"
8
9
  },
9
10
  "scripts": {
10
11
  "build": "tsc",
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
  {
@@ -609,7 +680,7 @@ const tools: ToolDefinition[] = [
609
680
  const toolMap = new Map(tools.map((tool) => [tool.name, tool]));
610
681
 
611
682
  const server = new Server(
612
- { name: "odds-api-mcp", version: "1.1.0" },
683
+ { name: "odds-api-mcp", version: "1.1.1" },
613
684
  { capabilities: { tools: {}, resources: {} } },
614
685
  );
615
686