odds-api-mcp-server 1.0.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 ADDED
@@ -0,0 +1,131 @@
1
+ # Odds-API.io MCP Server
2
+
3
+ Model Context Protocol (MCP) server for [Odds-API.io](https://odds-api.io) - providing AI tools like Claude, Cursor, and VS Code with direct access to sports betting odds data.
4
+
5
+ ## Features
6
+
7
+ - **12 API tools** for fetching sports, bookmakers, events, odds, value bets, and arbitrage opportunities
8
+ - **Documentation resources** for AI context
9
+ - **Real-time data** from 265 bookmakers across 34 sports
10
+
11
+ ## Installation
12
+
13
+ ### Claude Code CLI
14
+
15
+ ```bash
16
+ claude mcp add odds-api --env ODDS_API_KEY="your-api-key" -- npx -y odds-api-mcp-server
17
+ ```
18
+
19
+ ### Manual Installation
20
+
21
+ ```bash
22
+ npm install -g odds-api-mcp-server
23
+ ```
24
+
25
+ ### From Source
26
+
27
+ ```bash
28
+ git clone https://github.com/oddsapi-io/mcp-server
29
+ cd mcp-server
30
+ npm install
31
+ npm run build
32
+ ```
33
+
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
43
+
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):
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "odds-api": {
50
+ "command": "npx",
51
+ "args": ["-y", "odds-api-mcp-server"],
52
+ "env": {
53
+ "ODDS_API_KEY": "your-api-key"
54
+ }
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ ### Cursor
61
+
62
+ Add to your Cursor MCP settings:
63
+
64
+ ```json
65
+ {
66
+ "odds-api": {
67
+ "command": "npx",
68
+ "args": ["-y", "odds-api-mcp-server"],
69
+ "env": {
70
+ "ODDS_API_KEY": "your-api-key"
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Available Tools
77
+
78
+ | Tool | Description |
79
+ |------|-------------|
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 |
84
+ | `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 |
92
+
93
+ ## Available Resources
94
+
95
+ | Resource | Description |
96
+ |----------|-------------|
97
+ | `odds-api://documentation` | Full API documentation |
98
+ | `odds-api://openapi` | OpenAPI specification |
99
+
100
+ ## Example Usage
101
+
102
+ Once configured, you can ask your AI assistant:
103
+
104
+ - "Get me the list of available sports"
105
+ - "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"
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ # Install dependencies
114
+ npm install
115
+
116
+ # Build
117
+ npm run build
118
+
119
+ # Run in development mode
120
+ npm run dev
121
+ ```
122
+
123
+ ## License
124
+
125
+ MIT
126
+
127
+ ## Links
128
+
129
+ - [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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const API_BASE_URL = "https://api2.odds-api.io/v3";
8
+ const DOCS_BASE_URL = "https://docs.odds-api.io";
9
+ // Get API key from environment
10
+ const API_KEY = process.env.ODDS_API_KEY || "";
11
+ if (!API_KEY) {
12
+ console.error("Error: ODDS_API_KEY environment variable is required");
13
+ process.exit(1);
14
+ }
15
+ // Helper function to make API requests
16
+ async function apiRequest(endpoint, params = {}) {
17
+ const url = new URL(`${API_BASE_URL}${endpoint}`);
18
+ url.searchParams.set("apiKey", API_KEY);
19
+ for (const [key, value] of Object.entries(params)) {
20
+ if (value) {
21
+ url.searchParams.set(key, value);
22
+ }
23
+ }
24
+ const response = await fetch(url.toString());
25
+ if (!response.ok) {
26
+ const error = await response.text();
27
+ throw new Error(`API request failed: ${response.status} - ${error}`);
28
+ }
29
+ return response.json();
30
+ }
31
+ // Create the MCP server
32
+ const server = new index_js_1.Server({
33
+ name: "odds-api-mcp",
34
+ version: "1.0.0",
35
+ }, {
36
+ capabilities: {
37
+ tools: {},
38
+ resources: {},
39
+ },
40
+ });
41
+ // Define tools
42
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
43
+ return {
44
+ tools: [
45
+ {
46
+ name: "get_sports",
47
+ description: "Get list of all available sports. Returns sport name and slug for each sport.",
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {},
51
+ required: [],
52
+ },
53
+ },
54
+ {
55
+ name: "get_bookmakers",
56
+ description: "Get list of all available bookmakers. Returns bookmaker name and active status.",
57
+ inputSchema: {
58
+ type: "object",
59
+ properties: {},
60
+ required: [],
61
+ },
62
+ },
63
+ {
64
+ name: "get_leagues",
65
+ description: "Get leagues for a specific sport. Returns league name, slug, and event count.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ sport: {
70
+ type: "string",
71
+ description: "Sport slug (e.g., 'football', 'basketball', 'tennis')",
72
+ },
73
+ },
74
+ required: ["sport"],
75
+ },
76
+ },
77
+ {
78
+ name: "get_events",
79
+ description: "Get events for a sport, optionally filtered by league, status, or date range.",
80
+ inputSchema: {
81
+ type: "object",
82
+ properties: {
83
+ sport: {
84
+ type: "string",
85
+ description: "Sport slug (e.g., 'football', 'basketball')",
86
+ },
87
+ league: {
88
+ type: "string",
89
+ description: "Optional league slug (e.g., 'england-premier-league')",
90
+ },
91
+ status: {
92
+ type: "string",
93
+ description: "Optional comma-separated statuses (pending, live, settled)",
94
+ },
95
+ },
96
+ required: ["sport"],
97
+ },
98
+ },
99
+ {
100
+ name: "get_live_events",
101
+ description: "Get all currently live events, optionally filtered by sport.",
102
+ inputSchema: {
103
+ type: "object",
104
+ properties: {
105
+ sport: {
106
+ type: "string",
107
+ description: "Optional sport slug to filter live events",
108
+ },
109
+ },
110
+ required: [],
111
+ },
112
+ },
113
+ {
114
+ name: "search_events",
115
+ description: "Search events by team name or other text. Returns up to 10 matching events.",
116
+ inputSchema: {
117
+ type: "object",
118
+ properties: {
119
+ query: {
120
+ type: "string",
121
+ description: "Search term (minimum 3 characters)",
122
+ },
123
+ },
124
+ required: ["query"],
125
+ },
126
+ },
127
+ {
128
+ name: "get_odds",
129
+ description: "Get betting odds for a specific event from selected bookmakers.",
130
+ inputSchema: {
131
+ type: "object",
132
+ properties: {
133
+ eventId: {
134
+ type: "string",
135
+ description: "Event ID",
136
+ },
137
+ bookmakers: {
138
+ type: "string",
139
+ description: "Comma-separated list of bookmaker names (e.g., 'Bet365,Pinnacle,Unibet')",
140
+ },
141
+ },
142
+ required: ["eventId", "bookmakers"],
143
+ },
144
+ },
145
+ {
146
+ name: "get_multi_odds",
147
+ description: "Get odds for multiple events in a single request (up to 10 events). Counts as 1 API call.",
148
+ inputSchema: {
149
+ type: "object",
150
+ properties: {
151
+ eventIds: {
152
+ type: "string",
153
+ description: "Comma-separated list of event IDs (max 10)",
154
+ },
155
+ bookmakers: {
156
+ type: "string",
157
+ description: "Comma-separated list of bookmaker names",
158
+ },
159
+ },
160
+ required: ["eventIds", "bookmakers"],
161
+ },
162
+ },
163
+ {
164
+ name: "get_value_bets",
165
+ description: "Get value betting opportunities for a specific bookmaker. Returns bets where expected value is positive.",
166
+ inputSchema: {
167
+ type: "object",
168
+ properties: {
169
+ bookmaker: {
170
+ type: "string",
171
+ description: "Bookmaker name (e.g., 'Bet365')",
172
+ },
173
+ includeEventDetails: {
174
+ type: "boolean",
175
+ description: "Include full event details in response",
176
+ },
177
+ },
178
+ required: ["bookmaker"],
179
+ },
180
+ },
181
+ {
182
+ name: "get_arbitrage_bets",
183
+ description: "Get arbitrage betting opportunities across specified bookmakers.",
184
+ inputSchema: {
185
+ type: "object",
186
+ properties: {
187
+ bookmakers: {
188
+ type: "string",
189
+ description: "Comma-separated list of bookmaker names",
190
+ },
191
+ limit: {
192
+ type: "number",
193
+ description: "Maximum number of results (default 50, max 500)",
194
+ },
195
+ includeEventDetails: {
196
+ type: "boolean",
197
+ description: "Include full event details in response",
198
+ },
199
+ },
200
+ required: ["bookmakers"],
201
+ },
202
+ },
203
+ {
204
+ name: "get_participants",
205
+ description: "Get teams/participants for a sport, optionally filtered by search term.",
206
+ inputSchema: {
207
+ type: "object",
208
+ properties: {
209
+ sport: {
210
+ type: "string",
211
+ description: "Sport slug (e.g., 'football')",
212
+ },
213
+ search: {
214
+ type: "string",
215
+ description: "Optional search term to filter by name",
216
+ },
217
+ },
218
+ required: ["sport"],
219
+ },
220
+ },
221
+ {
222
+ name: "get_documentation",
223
+ description: "Get Odds-API.io documentation. Returns the full documentation text.",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {},
227
+ required: [],
228
+ },
229
+ },
230
+ ],
231
+ };
232
+ });
233
+ // Handle tool calls
234
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
235
+ const { name, arguments: args } = request.params;
236
+ try {
237
+ switch (name) {
238
+ case "get_sports": {
239
+ const data = await apiRequest("/sports");
240
+ return {
241
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
242
+ };
243
+ }
244
+ case "get_bookmakers": {
245
+ const data = await apiRequest("/bookmakers");
246
+ return {
247
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
248
+ };
249
+ }
250
+ case "get_leagues": {
251
+ const params = args;
252
+ const data = await apiRequest("/leagues", { sport: params.sport });
253
+ return {
254
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
255
+ };
256
+ }
257
+ case "get_events": {
258
+ const params = args;
259
+ const data = await apiRequest("/events", {
260
+ sport: params.sport,
261
+ league: params.league || "",
262
+ status: params.status || "",
263
+ });
264
+ return {
265
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
266
+ };
267
+ }
268
+ case "get_live_events": {
269
+ const params = args;
270
+ const data = await apiRequest("/events/live", {
271
+ sport: params.sport || "",
272
+ });
273
+ return {
274
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
275
+ };
276
+ }
277
+ case "search_events": {
278
+ const params = args;
279
+ const data = await apiRequest("/events/search", { query: params.query });
280
+ return {
281
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
282
+ };
283
+ }
284
+ case "get_odds": {
285
+ const params = args;
286
+ const data = await apiRequest("/odds", {
287
+ eventId: params.eventId,
288
+ bookmakers: params.bookmakers,
289
+ });
290
+ return {
291
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
292
+ };
293
+ }
294
+ case "get_multi_odds": {
295
+ const params = args;
296
+ const data = await apiRequest("/odds/multi", {
297
+ eventIds: params.eventIds,
298
+ bookmakers: params.bookmakers,
299
+ });
300
+ return {
301
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
302
+ };
303
+ }
304
+ case "get_value_bets": {
305
+ const params = args;
306
+ const data = await apiRequest("/value-bets", {
307
+ bookmaker: params.bookmaker,
308
+ includeEventDetails: params.includeEventDetails ? "true" : "false",
309
+ });
310
+ return {
311
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
312
+ };
313
+ }
314
+ case "get_arbitrage_bets": {
315
+ const params = args;
316
+ const data = await apiRequest("/arbitrage-bets", {
317
+ bookmakers: params.bookmakers,
318
+ limit: params.limit?.toString() || "",
319
+ includeEventDetails: params.includeEventDetails ? "true" : "false",
320
+ });
321
+ return {
322
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
323
+ };
324
+ }
325
+ case "get_participants": {
326
+ const params = args;
327
+ const data = await apiRequest("/participants", {
328
+ sport: params.sport,
329
+ search: params.search || "",
330
+ });
331
+ return {
332
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
333
+ };
334
+ }
335
+ case "get_documentation": {
336
+ const response = await fetch(`${DOCS_BASE_URL}/llms-full.txt`);
337
+ const text = await response.text();
338
+ return {
339
+ content: [{ type: "text", text }],
340
+ };
341
+ }
342
+ default:
343
+ throw new Error(`Unknown tool: ${name}`);
344
+ }
345
+ }
346
+ catch (error) {
347
+ const message = error instanceof Error ? error.message : "Unknown error";
348
+ return {
349
+ content: [{ type: "text", text: `Error: ${message}` }],
350
+ isError: true,
351
+ };
352
+ }
353
+ });
354
+ // Define resources
355
+ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
356
+ return {
357
+ resources: [
358
+ {
359
+ uri: "odds-api://documentation",
360
+ name: "Odds-API.io Documentation",
361
+ description: "Complete API documentation for Odds-API.io",
362
+ mimeType: "text/plain",
363
+ },
364
+ {
365
+ uri: "odds-api://openapi",
366
+ name: "OpenAPI Specification",
367
+ description: "OpenAPI/Swagger specification for Odds-API.io",
368
+ mimeType: "application/json",
369
+ },
370
+ ],
371
+ };
372
+ });
373
+ // Handle resource reads
374
+ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
375
+ const { uri } = request.params;
376
+ switch (uri) {
377
+ case "odds-api://documentation": {
378
+ const response = await fetch(`${DOCS_BASE_URL}/llms-full.txt`);
379
+ const text = await response.text();
380
+ return {
381
+ contents: [{ uri, mimeType: "text/plain", text }],
382
+ };
383
+ }
384
+ case "odds-api://openapi": {
385
+ const response = await fetch(`${DOCS_BASE_URL}/api-reference/openapi.json`);
386
+ const json = await response.json();
387
+ return {
388
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(json, null, 2) }],
389
+ };
390
+ }
391
+ default:
392
+ throw new Error(`Unknown resource: ${uri}`);
393
+ }
394
+ });
395
+ // Start the server
396
+ async function main() {
397
+ const transport = new stdio_js_1.StdioServerTransport();
398
+ await server.connect(transport);
399
+ console.error("Odds-API.io MCP Server running on stdio");
400
+ }
401
+ main().catch((error) => {
402
+ console.error("Fatal error:", error);
403
+ process.exit(1);
404
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "odds-api-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Odds-API.io - Access sports betting odds data from AI tools like Claude, Cursor, and VS Code",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "odds-api-mcp": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "ts-node src/index.ts"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "model-context-protocol",
17
+ "odds-api",
18
+ "sports-betting",
19
+ "odds",
20
+ "ai",
21
+ "claude",
22
+ "cursor"
23
+ ],
24
+ "author": "Odds-API.io",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/oddsapi-io/mcp-server"
29
+ },
30
+ "dependencies": {
31
+ "@modelcontextprotocol/sdk": "^0.5.0",
32
+ "zod": "^3.22.4"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.10.0",
36
+ "ts-node": "^10.9.2",
37
+ "typescript": "^5.3.2"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ }
42
+ }
package/src/index.ts ADDED
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ ListResourcesRequestSchema,
9
+ ReadResourceRequestSchema,
10
+ } from "@modelcontextprotocol/sdk/types.js";
11
+ import { z } from "zod";
12
+
13
+ const API_BASE_URL = "https://api2.odds-api.io/v3";
14
+ const DOCS_BASE_URL = "https://docs.odds-api.io";
15
+
16
+ // Get API key from environment
17
+ const API_KEY = process.env.ODDS_API_KEY || "";
18
+
19
+ if (!API_KEY) {
20
+ console.error("Error: ODDS_API_KEY environment variable is required");
21
+ process.exit(1);
22
+ }
23
+
24
+ // Helper function to make API requests
25
+ async function apiRequest(endpoint: string, params: Record<string, string> = {}) {
26
+ const url = new URL(`${API_BASE_URL}${endpoint}`);
27
+ url.searchParams.set("apiKey", API_KEY);
28
+
29
+ for (const [key, value] of Object.entries(params)) {
30
+ if (value) {
31
+ url.searchParams.set(key, value);
32
+ }
33
+ }
34
+
35
+ const response = await fetch(url.toString());
36
+
37
+ if (!response.ok) {
38
+ const error = await response.text();
39
+ throw new Error(`API request failed: ${response.status} - ${error}`);
40
+ }
41
+
42
+ return response.json();
43
+ }
44
+
45
+ // Create the MCP server
46
+ const server = new Server(
47
+ {
48
+ name: "odds-api-mcp",
49
+ version: "1.0.0",
50
+ },
51
+ {
52
+ capabilities: {
53
+ tools: {},
54
+ resources: {},
55
+ },
56
+ }
57
+ );
58
+
59
+ // Define tools
60
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
61
+ return {
62
+ tools: [
63
+ {
64
+ name: "get_sports",
65
+ description: "Get list of all available sports. Returns sport name and slug for each sport.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {},
69
+ required: [],
70
+ },
71
+ },
72
+ {
73
+ name: "get_bookmakers",
74
+ description: "Get list of all available bookmakers. Returns bookmaker name and active status.",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {},
78
+ required: [],
79
+ },
80
+ },
81
+ {
82
+ name: "get_leagues",
83
+ description: "Get leagues for a specific sport. Returns league name, slug, and event count.",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ sport: {
88
+ type: "string",
89
+ description: "Sport slug (e.g., 'football', 'basketball', 'tennis')",
90
+ },
91
+ },
92
+ required: ["sport"],
93
+ },
94
+ },
95
+ {
96
+ name: "get_events",
97
+ description: "Get events for a sport, optionally filtered by league, status, or date range.",
98
+ inputSchema: {
99
+ type: "object",
100
+ properties: {
101
+ sport: {
102
+ type: "string",
103
+ description: "Sport slug (e.g., 'football', 'basketball')",
104
+ },
105
+ league: {
106
+ type: "string",
107
+ description: "Optional league slug (e.g., 'england-premier-league')",
108
+ },
109
+ status: {
110
+ type: "string",
111
+ description: "Optional comma-separated statuses (pending, live, settled)",
112
+ },
113
+ },
114
+ required: ["sport"],
115
+ },
116
+ },
117
+ {
118
+ name: "get_live_events",
119
+ description: "Get all currently live events, optionally filtered by sport.",
120
+ inputSchema: {
121
+ type: "object",
122
+ properties: {
123
+ sport: {
124
+ type: "string",
125
+ description: "Optional sport slug to filter live events",
126
+ },
127
+ },
128
+ required: [],
129
+ },
130
+ },
131
+ {
132
+ name: "search_events",
133
+ description: "Search events by team name or other text. Returns up to 10 matching events.",
134
+ inputSchema: {
135
+ type: "object",
136
+ properties: {
137
+ query: {
138
+ type: "string",
139
+ description: "Search term (minimum 3 characters)",
140
+ },
141
+ },
142
+ required: ["query"],
143
+ },
144
+ },
145
+ {
146
+ name: "get_odds",
147
+ description: "Get betting odds for a specific event from selected bookmakers.",
148
+ inputSchema: {
149
+ type: "object",
150
+ properties: {
151
+ eventId: {
152
+ type: "string",
153
+ description: "Event ID",
154
+ },
155
+ bookmakers: {
156
+ type: "string",
157
+ description: "Comma-separated list of bookmaker names (e.g., 'Bet365,Pinnacle,Unibet')",
158
+ },
159
+ },
160
+ required: ["eventId", "bookmakers"],
161
+ },
162
+ },
163
+ {
164
+ name: "get_multi_odds",
165
+ description: "Get odds for multiple events in a single request (up to 10 events). Counts as 1 API call.",
166
+ inputSchema: {
167
+ type: "object",
168
+ properties: {
169
+ eventIds: {
170
+ type: "string",
171
+ description: "Comma-separated list of event IDs (max 10)",
172
+ },
173
+ bookmakers: {
174
+ type: "string",
175
+ description: "Comma-separated list of bookmaker names",
176
+ },
177
+ },
178
+ required: ["eventIds", "bookmakers"],
179
+ },
180
+ },
181
+ {
182
+ name: "get_value_bets",
183
+ description: "Get value betting opportunities for a specific bookmaker. Returns bets where expected value is positive.",
184
+ inputSchema: {
185
+ type: "object",
186
+ properties: {
187
+ bookmaker: {
188
+ type: "string",
189
+ description: "Bookmaker name (e.g., 'Bet365')",
190
+ },
191
+ includeEventDetails: {
192
+ type: "boolean",
193
+ description: "Include full event details in response",
194
+ },
195
+ },
196
+ required: ["bookmaker"],
197
+ },
198
+ },
199
+ {
200
+ name: "get_arbitrage_bets",
201
+ description: "Get arbitrage betting opportunities across specified bookmakers.",
202
+ inputSchema: {
203
+ type: "object",
204
+ properties: {
205
+ bookmakers: {
206
+ type: "string",
207
+ description: "Comma-separated list of bookmaker names",
208
+ },
209
+ limit: {
210
+ type: "number",
211
+ description: "Maximum number of results (default 50, max 500)",
212
+ },
213
+ includeEventDetails: {
214
+ type: "boolean",
215
+ description: "Include full event details in response",
216
+ },
217
+ },
218
+ required: ["bookmakers"],
219
+ },
220
+ },
221
+ {
222
+ name: "get_participants",
223
+ description: "Get teams/participants for a sport, optionally filtered by search term.",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {
227
+ sport: {
228
+ type: "string",
229
+ description: "Sport slug (e.g., 'football')",
230
+ },
231
+ search: {
232
+ type: "string",
233
+ description: "Optional search term to filter by name",
234
+ },
235
+ },
236
+ required: ["sport"],
237
+ },
238
+ },
239
+ {
240
+ name: "get_documentation",
241
+ description: "Get Odds-API.io documentation. Returns the full documentation text.",
242
+ inputSchema: {
243
+ type: "object",
244
+ properties: {},
245
+ required: [],
246
+ },
247
+ },
248
+ ],
249
+ };
250
+ });
251
+
252
+ // Handle tool calls
253
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
254
+ const { name, arguments: args } = request.params;
255
+
256
+ try {
257
+ switch (name) {
258
+ case "get_sports": {
259
+ const data = await apiRequest("/sports");
260
+ return {
261
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
262
+ };
263
+ }
264
+
265
+ case "get_bookmakers": {
266
+ const data = await apiRequest("/bookmakers");
267
+ return {
268
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
269
+ };
270
+ }
271
+
272
+ case "get_leagues": {
273
+ const params = args as { sport: string };
274
+ const data = await apiRequest("/leagues", { sport: params.sport });
275
+ return {
276
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
277
+ };
278
+ }
279
+
280
+ case "get_events": {
281
+ const params = args as { sport: string; league?: string; status?: string };
282
+ const data = await apiRequest("/events", {
283
+ sport: params.sport,
284
+ league: params.league || "",
285
+ status: params.status || "",
286
+ });
287
+ return {
288
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
289
+ };
290
+ }
291
+
292
+ case "get_live_events": {
293
+ const params = args as { sport?: string };
294
+ const data = await apiRequest("/events/live", {
295
+ sport: params.sport || "",
296
+ });
297
+ return {
298
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
299
+ };
300
+ }
301
+
302
+ case "search_events": {
303
+ const params = args as { query: string };
304
+ const data = await apiRequest("/events/search", { query: params.query });
305
+ return {
306
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
307
+ };
308
+ }
309
+
310
+ case "get_odds": {
311
+ const params = args as { eventId: string; bookmakers: string };
312
+ const data = await apiRequest("/odds", {
313
+ eventId: params.eventId,
314
+ bookmakers: params.bookmakers,
315
+ });
316
+ return {
317
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
318
+ };
319
+ }
320
+
321
+ case "get_multi_odds": {
322
+ const params = args as { eventIds: string; bookmakers: string };
323
+ const data = await apiRequest("/odds/multi", {
324
+ eventIds: params.eventIds,
325
+ bookmakers: params.bookmakers,
326
+ });
327
+ return {
328
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
329
+ };
330
+ }
331
+
332
+ case "get_value_bets": {
333
+ const params = args as { bookmaker: string; includeEventDetails?: boolean };
334
+ const data = await apiRequest("/value-bets", {
335
+ bookmaker: params.bookmaker,
336
+ includeEventDetails: params.includeEventDetails ? "true" : "false",
337
+ });
338
+ return {
339
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
340
+ };
341
+ }
342
+
343
+ case "get_arbitrage_bets": {
344
+ const params = args as { bookmakers: string; limit?: number; includeEventDetails?: boolean };
345
+ const data = await apiRequest("/arbitrage-bets", {
346
+ bookmakers: params.bookmakers,
347
+ limit: params.limit?.toString() || "",
348
+ includeEventDetails: params.includeEventDetails ? "true" : "false",
349
+ });
350
+ return {
351
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
352
+ };
353
+ }
354
+
355
+ case "get_participants": {
356
+ const params = args as { sport: string; search?: string };
357
+ const data = await apiRequest("/participants", {
358
+ sport: params.sport,
359
+ search: params.search || "",
360
+ });
361
+ return {
362
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
363
+ };
364
+ }
365
+
366
+ case "get_documentation": {
367
+ const response = await fetch(`${DOCS_BASE_URL}/llms-full.txt`);
368
+ const text = await response.text();
369
+ return {
370
+ content: [{ type: "text", text }],
371
+ };
372
+ }
373
+
374
+ default:
375
+ throw new Error(`Unknown tool: ${name}`);
376
+ }
377
+ } catch (error) {
378
+ const message = error instanceof Error ? error.message : "Unknown error";
379
+ return {
380
+ content: [{ type: "text", text: `Error: ${message}` }],
381
+ isError: true,
382
+ };
383
+ }
384
+ });
385
+
386
+ // Define resources
387
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
388
+ return {
389
+ resources: [
390
+ {
391
+ uri: "odds-api://documentation",
392
+ name: "Odds-API.io Documentation",
393
+ description: "Complete API documentation for Odds-API.io",
394
+ mimeType: "text/plain",
395
+ },
396
+ {
397
+ uri: "odds-api://openapi",
398
+ name: "OpenAPI Specification",
399
+ description: "OpenAPI/Swagger specification for Odds-API.io",
400
+ mimeType: "application/json",
401
+ },
402
+ ],
403
+ };
404
+ });
405
+
406
+ // Handle resource reads
407
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
408
+ const { uri } = request.params;
409
+
410
+ switch (uri) {
411
+ case "odds-api://documentation": {
412
+ const response = await fetch(`${DOCS_BASE_URL}/llms-full.txt`);
413
+ const text = await response.text();
414
+ return {
415
+ contents: [{ uri, mimeType: "text/plain", text }],
416
+ };
417
+ }
418
+
419
+ case "odds-api://openapi": {
420
+ const response = await fetch(`${DOCS_BASE_URL}/api-reference/openapi.json`);
421
+ const json = await response.json();
422
+ return {
423
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(json, null, 2) }],
424
+ };
425
+ }
426
+
427
+ default:
428
+ throw new Error(`Unknown resource: ${uri}`);
429
+ }
430
+ });
431
+
432
+ // Start the server
433
+ async function main() {
434
+ const transport = new StdioServerTransport();
435
+ await server.connect(transport);
436
+ console.error("Odds-API.io MCP Server running on stdio");
437
+ }
438
+
439
+ main().catch((error) => {
440
+ console.error("Fatal error:", error);
441
+ process.exit(1);
442
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }