#
tokens: 44382/50000 41/44 files (page 1/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 3. Use http://codebase.md/hrgarber/wagyu_mcp_hackathon?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .github
│   ├── hooks
│   │   ├── modules
│   │   │   ├── api-key-check.sh
│   │   │   └── env-check.sh
│   │   └── pre-commit
│   ├── scripts
│   │   └── auto-setup.sh
│   └── workflows
│       └── auto-setup-hooks.yml
├── .gitignore
├── assets
│   └── images
│       └── wagyu_ninja.png
├── docs
│   ├── README.md
│   └── reagan_planning
│       ├── meeting with reagan
│       │   └── transcript.md
│       └── transcript_insights.md
├── LICENSE
├── old
│   ├── docs
│   │   ├── 2025-01-10_odds_api_v4.md
│   │   ├── 2025-01-15_mcp_infra.md
│   │   ├── 2025-01-20_api_key_security.md
│   │   ├── 2025-02-10_pip_install_fix_plan.md
│   │   ├── 2025-02-15_python_odds_api_fix_postmortem.md
│   │   └── 2025-02-20_task_context.md
│   ├── espn_nonbetting_api
│   │   └── espn_api_endpoints.md
│   ├── README.md
│   └── reagan_planning
│       └── mcp_testing_approach.md
├── README.md
└── wagyu_sports
    ├── __init__.py
    ├── build
    │   ├── pyproject.toml
    │   ├── requirements.txt
    │   └── setup.py
    ├── config
    │   ├── .env.example
    │   └── pytest.ini
    ├── conftest.py
    ├── docs
    │   └── LICENSE
    ├── examples
    │   ├── advanced_example.py
    │   ├── example.py
    │   ├── fetch_nba_odds.py
    │   ├── verify_import.py
    │   └── verify_install.py
    ├── Makefile
    ├── mcp_server
    │   ├── __init__.py
    │   ├── capture_live_responses.py
    │   ├── mocks_live
    │   │   ├── nba_games_live.json
    │   │   ├── quota_info_live.json
    │   │   ├── README.md
    │   │   └── sports_list_live.json
    │   ├── odds_client_server.py
    │   ├── odds_client.py
    │   ├── README.md
    │   └── test_server.py
    ├── odds_client.py
    ├── README.md
    ├── tests
    │   ├── README.md
    │   ├── test_odds_api.py
    │   ├── test_odds_mcp_server.py
    │   └── test_simple_mcp.py
    └── utils.py
```

# Files

--------------------------------------------------------------------------------
/wagyu_sports/config/.env.example:
--------------------------------------------------------------------------------

```
1 | # The Odds API Key
2 | # Get your API key from https://the-odds-api.com/
3 | ODDS_API_KEY=your_api_key_here
4 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Node.js
 2 | node_modules/
 3 | .env
 4 | 
 5 | # Python
 6 | __pycache__/
 7 | *.py[cod]
 8 | *$py.class
 9 | .pytest_cache/
10 | .coverage
11 | htmlcov/
12 | .tox/
13 | .venv/
14 | venv/
15 | env/
16 | *.egg-info/
17 | dist/
18 | build/
19 | 
20 | # macOS
21 | .DS_Store
22 | .AppleDouble
23 | .LSOverride
24 | Icon
25 | ._*
26 | .Spotlight-V100
27 | .Trashes
28 | .fseventsd
29 | .DocumentRevisions-V100
30 | .TemporaryItems
31 | .VolumeIcon.icns
32 | .com.apple.timemachine.donotpresent
33 | 
34 | # IDE
35 | .idea/
36 | .vscode/
37 | *.swp
38 | *.swo
39 | 
```

--------------------------------------------------------------------------------
/old/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Documentation Archive
 2 | 
 3 | This directory contains historical documentation to track the project's development journey.
 4 | 
 5 | ## Purpose
 6 | 
 7 | The purpose of this archive is to chronologically document the development process through markdown and text files. This helps preserve the history of decisions, plans, and implementations without modifying the original documents.
 8 | 
 9 | ## Structure
10 | 
11 | ```
12 | old/
13 | ├── README.md  (this file)
14 | └── docs/      (all historical documents)
15 | ```
16 | 
17 | ## Adding Documents
18 | 
19 | 1. When a document becomes historical, copy it to the `docs/` directory
20 | 2. Add date prefix: `YYYY-MM-DD_filename.md`
21 | 3. Don't modify documents after archiving
22 | 
23 | ## Guidelines
24 | 
25 | - Only add markdown (.md) and text (.txt) files
26 | - Always include the date prefix in the format `YYYY-MM-DD_`
27 | - Original files should remain in their original locations
28 | - This system is intentionally minimal and can evolve as needed
29 | 
30 | All MD and TXT files are welcome. No complex organization required.
31 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/mcp_server/mocks_live/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Live API Response Captures
 2 | 
 3 | This directory contains captures of live API responses from the Odds API. These captures can be used for creating updated mock data for testing.
 4 | 
 5 | ## Using Live Mode
 6 | 
 7 | The MCP server has been modified to support overriding the test mode setting on a per-call basis. To use the live API (which costs money per call), set the `use_test_mode` parameter to `false` in your tool calls.
 8 | 
 9 | ### Example Usage
10 | 
11 | ```python
12 | # Get sports list using live API
13 | <use_mcp_tool>
14 | <server_name>wagyu-sports</server_name>
15 | <tool_name>get_sports</tool_name>
16 | <arguments>
17 | {
18 |   "all_sports": true,
19 |   "use_test_mode": false
20 | }
21 | </arguments>
22 | </use_mcp_tool>
23 | 
24 | # Get NBA odds using live API
25 | <use_mcp_tool>
26 | <server_name>wagyu-sports</server_name>
27 | <tool_name>get_odds</tool_name>
28 | <arguments>
29 | {
30 |   "sport": "basketball_nba",
31 |   "regions": "us",
32 |   "markets": "h2h,spreads",
33 |   "use_test_mode": false
34 | }
35 | </arguments>
36 | </use_mcp_tool>
37 | 
38 | # Get quota information using live API
39 | <use_mcp_tool>
40 | <server_name>wagyu-sports</server_name>
41 | <tool_name>get_quota_info</tool_name>
42 | <arguments>
43 | {
44 |   "use_test_mode": false
45 | }
46 | </arguments>
47 | </use_mcp_tool>
48 | ```
49 | 
50 | ## Important Notes
51 | 
52 | 1. **Cost Awareness**: Each live API call costs money. Use sparingly and only when necessary.
53 | 2. **Server Restart Required**: After modifying the server code, the MCP server needs to be restarted for changes to take effect.
54 | 3. **API Key**: The live mode requires a valid API key, which is already configured in the MCP settings.
55 | 
56 | ## Captured Responses
57 | 
58 | The following live API responses have been captured:
59 | 
60 | - `sports_list_live.json`: List of available sports
61 | - `nba_games_live.json`: NBA game odds data
62 | - `quota_info_live.json`: API quota information
63 | 
```

--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # API Documentation Collection
 2 | 
 3 | This directory contains detailed documentation for various APIs used in our projects. Each markdown file provides comprehensive information about an API's capabilities, endpoints, and integration details. These documents serve as a reference for both human developers and AI assistants when building applications.
 4 | 
 5 | ## Documentation Notice
 6 | 
 7 | **Important**: Historical documentation has been moved to the `/old/docs/` directory with date prefixes for better chronological tracking. If you're looking for previous documentation, please check there.
 8 | 
 9 | ## Documentation Structure
10 | 
11 | Each API documentation file follows a consistent structure:
12 | 
13 | 1. **Overview** - Basic information about the API
14 | 2. **Authentication** - How to authenticate with the API
15 | 3. **Available Endpoints** - Detailed endpoint documentation
16 | 4. **Integration Notes** - Best practices and implementation details
17 | 5. **Use Cases** - Common application scenarios
18 | 6. **Error Handling** - How to handle API errors
19 | 7. **Rate Limiting** - Usage limits and quotas
20 | 
21 | ## Purpose
22 | 
23 | These documentation files are designed to be:
24 | 
25 | 1. **AI-Readable** - Structured in a way that AI can easily parse and understand the capabilities
26 | 2. **Comprehensive** - Including all relevant details about the API
27 | 3. **Practical** - Focusing on real-world usage and integration
28 | 4. **Maintainable** - Easy to update as APIs evolve
29 | 
30 | ## Adding New API Documentation
31 | 
32 | When adding documentation for a new API:
33 | 
34 | 1. Create a new markdown file named appropriately (e.g., `api_name_v1.md`)
35 | 2. Follow the consistent documentation structure
36 | 3. Include all relevant details about authentication, endpoints, and usage
37 | 4. Update this README to include the new API documentation
38 | 
39 | ## Archiving Documentation
40 | 
41 | When documentation becomes historical:
42 | 
43 | 1. Move it to the `/old/docs/` directory
44 | 2. Add a date prefix in the format `YYYY-MM-DD_filename.md`
45 | 3. See `/old/README.md` for more details on the archiving system
46 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Wagyu Sports
  2 | 
  3 | A Python client for sports betting data with MCP server integration.
  4 | 
  5 | ```mermaid
  6 | graph LR
  7 |     User([Your Code]) --> Client[Wagyu Sports Client] --> API[The Odds API]
  8 |     MCP[MCP Server] --> Client
  9 |     Client --> MCP
 10 |     API --> Client --> User
 11 |     
 12 |     style User fill:#f8f8f8,stroke:#666,stroke-width:1px,color:#000
 13 |     style Client fill:#4285F4,stroke:#2965C9,stroke-width:2px,color:#fff
 14 |     style API fill:#F5F5F5,stroke:#999,stroke-width:1px,color:#000
 15 |     style MCP fill:#34A853,stroke:#1E8E3E,stroke-width:2px,color:#fff
 16 | ```
 17 | 
 18 | ## Directory Structure
 19 | 
 20 | The project has been reorganized for better maintainability:
 21 | - `build/` - Build-related files (pyproject.toml, requirements.txt, setup.py)
 22 | - `config/` - Configuration files (.env.example, pytest.ini)
 23 | - `docs/` - Documentation (LICENSE, README.md)
 24 | - `examples/` - Example scripts
 25 | - `mcp_server/` - Model Context Protocol (MCP) server implementation
 26 | - `tests/` - Test files
 27 | 
 28 | ## Installation
 29 | 
 30 | ```bash
 31 | # Development installation
 32 | uvx install -e .
 33 | 
 34 | # User installation
 35 | uv install wagyu_sports
 36 | 
 37 | # Set up API key
 38 | cp config/.env.example config/.env
 39 | # Edit .env and add your API key from https://the-odds-api.com/
 40 | ```
 41 | 
 42 | ## Quick Start
 43 | 
 44 | ```python
 45 | from wagyu_sports import OddsClient
 46 | import os
 47 | from dotenv import load_dotenv
 48 | 
 49 | # Load API key
 50 | load_dotenv(dotenv_path="config/.env")
 51 | api_key = os.getenv("ODDS_API_KEY")
 52 | 
 53 | # Create client and get sports
 54 | client = OddsClient(api_key)
 55 | sports = client.get_sports()
 56 | print(f"Available sports: {len(sports['data'])}")
 57 | ```
 58 | 
 59 | ## Features
 60 | 
 61 | - Access to sports betting data endpoints
 62 | - Track API usage through response headers
 63 | - Support for all API parameters and options
 64 | 
 65 | ## Examples
 66 | 
 67 | See the `examples/` directory for usage patterns:
 68 | - `examples/example.py`: Basic usage
 69 | - `examples/advanced_example.py`: Advanced features
 70 | - `examples/verify_install.py`: Verify installation
 71 | - `examples/fetch_nba_odds.py`: Fetch NBA odds example
 72 | - `examples/verify_import.py`: Simple import verification
 73 | 
 74 | ## Testing
 75 | 
 76 | The testing suite has been cleaned up and improved for better organization and reliability. Run the tests using pytest:
 77 | 
 78 | ```bash
 79 | # Install test dependencies
 80 | uvx install pytest pytest-asyncio
 81 | 
 82 | # Run all tests
 83 | uvx run pytest --rootdir=. -c config/pytest.ini
 84 | 
 85 | # Run specific test file
 86 | uvx run pytest tests/test_simple_mcp.py
 87 | ```
 88 | 
 89 | Or use the Makefile:
 90 | 
 91 | ```bash
 92 | make test
 93 | ```
 94 | 
 95 | The test suite includes:
 96 | - **API Client Tests**: Tests for the core Odds API client functionality
 97 | - **MCP Server Tests**: Tests for the MCP server implementation
 98 |   - **Client-based tests**: Test the full MCP protocol implementation
 99 |   - **Direct tests**: Simpler tests that directly test server methods
100 | 
101 | See the `tests/README.md` file for more details on the testing approach.
102 | 
103 | ## For MCP Server Information
104 | 
105 | See the main README.md file for details on running and configuring the MCP server.
106 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/mcp_server/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Wagyu Sports MCP Server
 2 | 
 3 | This directory contains a Model Context Protocol (MCP) server implementation for the Wagyu Sports API. The MCP server wraps the existing `OddsClient` and exposes its functionality through the standardized MCP interface.
 4 | 
 5 | ## Features
 6 | 
 7 | - Exposes sports betting data through MCP tools
 8 | - Supports test mode with mock data for development and testing
 9 | - Compatible with any MCP client (Claude Desktop, Cline, etc.)
10 | 
11 | ## Available Tools
12 | 
13 | The server exposes the following tools:
14 | 
15 | - `get_sports`: Get a list of available sports
16 | - `get_odds`: Get odds for a specific sport
17 | - `get_quota_info`: Get API quota information
18 | 
19 | ## Integration with MCP Clients
20 | 
21 | ### Integration with Cline
22 | 
23 | To use this MCP server with Cline:
24 | 
25 | 1. Add the server to Cline's MCP settings file located at:
26 |    - macOS: `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
27 |    - Windows: `%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json`
28 | 
29 | 2. Add the following configuration:
30 |    ```json
31 |    {
32 |      "mcpServers": {
33 |        "wagyu-sports": {
34 |          "command": "python",
35 |          "args": ["/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py"],
36 |          "env": {
37 |            "ODDS_API_KEY": "your_api_key"
38 |          },
39 |          "disabled": false,
40 |          "autoApprove": []
41 |        }
42 |      }
43 |    }
44 |    ```
45 | 
46 | 3. Replace `/path/to/wagyu_mcp_hackathon` with the actual path to your project
47 | 4. Replace `your_api_key` with your actual API key from [The Odds API](https://the-odds-api.com/)
48 | 5. Restart Cline
49 | 
50 | ### Integration with Claude Desktop
51 | 
52 | To use this MCP server with Claude Desktop:
53 | 
54 | 1. Add the server to Claude Desktop's configuration file located at:
55 |    - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
56 |    - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
57 | 
58 | 2. Add the following configuration:
59 |    ```json
60 |    {
61 |      "mcpServers": {
62 |        "wagyu-sports": {
63 |          "command": "python",
64 |          "args": ["/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py"],
65 |          "env": {
66 |            "ODDS_API_KEY": "your_api_key"
67 |          },
68 |          "disabled": false,
69 |          "autoApprove": []
70 |        }
71 |      }
72 |    }
73 |    ```
74 | 
75 | 3. Replace `/path/to/wagyu_mcp_hackathon` with the actual path to your project
76 | 4. Replace `your_api_key` with your actual API key from [The Odds API](https://the-odds-api.com/)
77 | 5. Restart Claude Desktop
78 | 
79 | ## Test Mode
80 | 
81 | The server can be run in test mode by adding the `--test-mode` flag to the command:
82 | 
83 | ```json
84 | "args": ["/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py", "--test-mode"]
85 | ```
86 | 
87 | In test mode, the server uses mock data from the `mocks_live/` directory instead of making real API calls, which:
88 | - Doesn't require an API key
89 | - Doesn't consume your API quota
90 | - Works offline
91 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/tests/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Wagyu Sports MCP Tests
  2 | 
  3 | This directory contains tests for the Wagyu Sports MCP server implementation.
  4 | 
  5 | ## Test Files
  6 | 
  7 | - `test_odds_api.py` - Tests for the core Odds API client
  8 | - `test_odds_mcp_server.py` - Tests for the MCP server implementation
  9 | - `test_simple_mcp.py` - Simple direct tests for the MCP server functionality
 10 | 
 11 | ## How to Run the Tests
 12 | 
 13 | The project uses pytest for running tests. The configuration is in `wagyu_sports/config/pytest.ini`.
 14 | 
 15 | ### Using pytest directly
 16 | 
 17 | ```bash
 18 | # Run all tests from the wagyu_sports directory
 19 | cd wagyu_sports
 20 | pytest
 21 | 
 22 | # Run specific test file
 23 | pytest tests/test_odds_mcp_server.py
 24 | 
 25 | # Run with verbose output (already default in pytest.ini)
 26 | pytest tests/test_odds_mcp_server.py
 27 | 
 28 | # Run a specific test function
 29 | pytest tests/test_odds_mcp_server.py::test_get_sports
 30 | ```
 31 | 
 32 | ### Environment Setup
 33 | 
 34 | The tests use the `test_mode=True` flag to run with mock data instead of making real API calls. This is handled automatically in the test code.
 35 | 
 36 | ### Pytest Configuration
 37 | 
 38 | The project's pytest.ini configuration:
 39 | ```ini
 40 | [pytest]
 41 | testpaths = tests
 42 | python_files = test_*.py
 43 | python_functions = test_*
 44 | addopts = -v
 45 | ```
 46 | 
 47 | This configuration automatically:
 48 | - Looks for tests in the `tests` directory
 49 | - Runs files that start with `test_`
 50 | - Runs functions that start with `test_`
 51 | - Uses verbose output by default
 52 | 
 53 | ## How to Add New Tests
 54 | 
 55 | ### Test Approaches
 56 | 
 57 | There are two main approaches to testing the MCP server:
 58 | 
 59 | 1. **Client-based testing** (in `test_odds_mcp_server.py`):
 60 |    - Uses the MCP client session to test the server through the MCP protocol
 61 |    - Tests the full protocol implementation
 62 |    - Good for integration testing
 63 | 
 64 | 2. **Direct testing** (in `test_simple_mcp.py`):
 65 |    - Tests the server methods directly
 66 |    - Faster and simpler for testing core functionality
 67 |    - Good for unit testing
 68 | 
 69 | ### Client-Based Test Structure
 70 | 
 71 | Client-based tests for the MCP server should follow this pattern:
 72 | 
 73 | ```python
 74 | @pytest.mark.anyio
 75 | async def test_your_feature():
 76 |     """Test description"""
 77 |     # Initialize server in test mode
 78 |     server = OddsMcpServer(test_mode=True)
 79 |     
 80 |     # Create client session
 81 |     async with client_session(server.server) as client:
 82 |         # Call the tool
 83 |         result = await client.call_tool("tool_name", {"param": "value"})
 84 |         
 85 |         # Assert results
 86 |         assert len(result.content) > 0
 87 |         content = result.content[0]
 88 |         assert isinstance(content, TextContent)
 89 |         
 90 |         # Add specific assertions for your test case
 91 |         assert "expected_value" in content.text
 92 | ```
 93 | 
 94 | ### Direct Test Structure
 95 | 
 96 | Direct tests access the server methods directly:
 97 | 
 98 | ```python
 99 | @pytest.mark.asyncio
100 | async def test_direct_method():
101 |     """Test description"""
102 |     # Initialize server in test mode
103 |     server = OddsMcpServer(test_mode=True)
104 |     
105 |     # Test method directly
106 |     mock_data = await server._get_mock_data("data_file.json")
107 |     
108 |     # Parse and verify the response
109 |     data = json.loads(mock_data)
110 |     assert "expected_key" in data
111 |     # Add more assertions...
112 | ```
113 | 
114 | ### Testing New Tools
115 | 
116 | When adding a new tool to the MCP server:
117 | 
118 | 1. Add the tool implementation to `odds_client_server.py`
119 | 2. Create a mock data file in `wagyu_sports/mcp_server/mocks_live/` if needed
120 | 3. Add a test function in `test_odds_mcp_server.py` following the pattern above
121 | 4. Ensure your test verifies both the structure and content of the response
122 | 
123 | ### Testing with Different Parameters
124 | 
125 | To test a tool with different parameters:
126 | 
127 | ```python
128 | @pytest.mark.anyio
129 | async def test_tool_with_params():
130 |     server = OddsMcpServer(test_mode=True)
131 |     
132 |     async with client_session(server.server) as client:
133 |         result = await client.call_tool(
134 |             "tool_name", 
135 |             {
136 |                 "param1": "value1",
137 |                 "param2": "value2"
138 |             }
139 |         )
140 |         # Assertions...
141 | ```
142 | 
143 | ### Testing Resources
144 | 
145 | To test MCP resources:
146 | 
147 | ```python
148 | @pytest.mark.anyio
149 | async def test_resource():
150 |     server = OddsMcpServer(test_mode=True)
151 |     
152 |     async with client_session(server.server) as client:
153 |         # List resources
154 |         resources = await client.list_resources()
155 |         
156 |         # Read a resource
157 |         result = await client.read_resource("resource://uri")
158 |         
159 |         # Assertions...
160 | ```
161 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Wagyu Sports MCP Server
  2 | 
  3 | <p align="center">
  4 |   <img src="assets/images/wagyu_ninja.png" width="250" alt="Wagyu Sports Logo">
  5 | </p>
  6 | 
  7 | A Model Context Protocol (MCP) server for sports betting data, providing access to The Odds API through Claude and other MCP-compatible AI assistants.
  8 | 
  9 | ```mermaid
 10 | graph LR
 11 |     Claude([Claude]) --> MCP[Wagyu Sports MCP]
 12 |     MCP --> API[The Odds API]
 13 |     API --> MCP --> Claude
 14 |     
 15 |     style Claude fill:#f8f8f8,stroke:#666,stroke-width:1px,color:#000
 16 |     style MCP fill:#34A853,stroke:#1E8E3E,stroke-width:2px,color:#fff
 17 |     style API fill:#F5F5F5,stroke:#999,stroke-width:1px,color:#000
 18 | ```
 19 | 
 20 | ## Quick Setup
 21 | 
 22 | 1. **Clone the repository**:
 23 |    ```bash
 24 |    # Clone the repository
 25 |    git clone https://github.com/your-username/wagyu_mcp_hackathon.git
 26 |    cd wagyu_mcp_hackathon
 27 |    ```
 28 | 
 29 | 2. **Install the package**:
 30 |    ```bash
 31 |    # Using pip (recommended)
 32 |    pip install -e .
 33 |    
 34 |    # Or using uv (alternative)
 35 |    uv install -e .
 36 |    ```
 37 | 
 38 | 3. **Add to your MCP configuration**:
 39 | 
 40 |    For Cline, add to `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`:
 41 | 
 42 |    ```json
 43 |    {
 44 |      "mcpServers": {
 45 |        "wagyu-sports": {
 46 |          "command": "python",
 47 |          "args": ["/absolute/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py", "--test-mode"],
 48 |          "env": {
 49 |            "ODDS_API_KEY": "your_api_key_here"
 50 |          },
 51 |          "disabled": false,
 52 |          "autoApprove": []
 53 |        }
 54 |      }
 55 |    }
 56 |    ```
 57 | 
 58 |    For Claude Desktop, add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
 59 | 
 60 |    ```json
 61 |    {
 62 |      "mcpServers": {
 63 |        "wagyu-sports": {
 64 |          "command": "python",
 65 |          "args": ["/absolute/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py", "--test-mode"],
 66 |          "env": {
 67 |            "ODDS_API_KEY": "your_api_key_here"
 68 |          },
 69 |          "disabled": false,
 70 |          "autoApprove": []
 71 |        }
 72 |      }
 73 |    }
 74 |    ```
 75 | 
 76 |    > **IMPORTANT**: Replace `/absolute/path/to/wagyu_mcp_hackathon` with the actual full path to your repository. For example: `/Users/john/Documents/hackathon/wagyu_mcp_hackathon`.
 77 | 
 78 | 4. **Get an API key** from [The Odds API](https://the-odds-api.com/) and replace `your_api_key_here` in the configuration.
 79 | 
 80 | 5. **Restart your MCP client** (Cline or Claude Desktop).
 81 | 
 82 | ## Available Tools
 83 | 
 84 | The MCP server provides the following tools:
 85 | 
 86 | - `get_sports`: Get a list of available sports
 87 | - `get_odds`: Get odds for a specific sport
 88 | - `get_quota_info`: Get API quota information
 89 | 
 90 | ## Test Mode vs. Real Mode
 91 | 
 92 | ### Test Mode (Recommended for Getting Started)
 93 | 
 94 | Test mode uses mock data instead of making real API calls. This is useful for:
 95 | - Development and testing without API rate limits
 96 | - Demos and presentations
 97 | - Learning how to use the MCP server
 98 | 
 99 | To use test mode:
100 | 1. Set `--test-mode` in your MCP configuration (as shown in the Quick Setup)
101 | 2. No API key is required
102 | 3. The server will return consistent mock data for all requests
103 | 
104 | Example configuration for test mode:
105 | ```json
106 | "args": ["/absolute/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py", "--test-mode"]
107 | ```
108 | 
109 | ### Real Mode
110 | 
111 | Real mode makes actual API calls to The Odds API. This is necessary for:
112 | - Getting real-time sports betting data
113 | - Production applications
114 | - Accurate odds information
115 | 
116 | To use real mode:
117 | 1. Remove the `--test-mode` flag from your MCP configuration
118 | 2. Provide a valid API key from The Odds API
119 | 3. Be aware of API rate limits (typically 500 requests per month for free tier)
120 | 
121 | Example configuration for real mode:
122 | ```json
123 | "args": ["/absolute/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py"],
124 | "env": {
125 |   "ODDS_API_KEY": "your_actual_api_key_here"
126 | }
127 | ```
128 | 
129 | You can also run the server directly with:
130 | ```bash
131 | python /path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py --api-key=your_api_key_here
132 | ```
133 | 
134 | ## Development
135 | 
136 | For development and testing:
137 | 
138 | ```bash
139 | # Clone the repository
140 | git clone https://github.com/your-username/wagyu_mcp_hackathon.git
141 | cd wagyu_mcp_hackathon
142 | 
143 | # Install in development mode
144 | pip install -e .
145 | 
146 | # Run tests
147 | python -m pytest wagyu_sports/tests
148 | 
149 | # Run the server directly (test mode)
150 | python wagyu_sports/mcp_server/test_server.py --test-mode
151 | 
152 | # Run the server directly (real mode)
153 | python wagyu_sports/mcp_server/test_server.py --api-key=your_api_key_here
154 | ```
155 | 
156 | > **Note**: This repository includes a post-commit Git hook that automatically cleans up Python cache files (`__pycache__`, `.pyc`, `.pyo`, `.pyd`) and `.pytest_cache` directories after each commit.
157 | 
158 | ## Project Structure
159 | 
160 | - `wagyu_sports/mcp_server/` - MCP server implementation
161 | - `wagyu_sports/tests/` - Test files
162 | - `wagyu_sports/examples/` - Example scripts
163 | 
164 | ## For More Information
165 | 
166 | See the `wagyu_sports/README.md` file for details on using the Python client directly.
167 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/config/pytest.ini:
--------------------------------------------------------------------------------

```
1 | [pytest]
2 | testpaths = tests
3 | python_files = test_*.py
4 | python_functions = test_*
5 | addopts = -v
6 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/mcp_server/mocks_live/quota_info_live.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "_metadata": {
 3 |     "captured_at": "2025-03-03T01:50:08-08:00",
 4 |     "description": "Live data captured from the Odds API",
 5 |     "tool": "get_quota_info",
 6 |     "parameters": {}
 7 |   },
 8 |   "remaining_requests": "488",
 9 |   "used_requests": "12"
10 | }
11 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/conftest.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Configuration file for pytest.
 3 | 
 4 | This file sets up the Python path for tests to ensure imports work correctly.
 5 | """
 6 | import os
 7 | import sys
 8 | 
 9 | # Add the project root to the Python path
10 | sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
11 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/mcp_server/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Wagyu Sports MCP Server
 3 | 
 4 | This module provides an MCP server implementation for the Wagyu Sports API.
 5 | """
 6 | 
 7 | try:
 8 |     # When imported as a package
 9 |     from .odds_client_server import OddsMcpServer
10 | except ImportError:
11 |     # When run directly
12 |     from odds_client_server import OddsMcpServer
13 | 
14 | __all__ = ["OddsMcpServer"]
15 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Wagyu Sports Package
 3 | 
 4 | This package provides a client for sports betting data, allowing users to fetch
 5 | sports betting data including live odds, scores, and event information.
 6 | """
 7 | 
 8 | from wagyu_sports.odds_client import OddsClient
 9 | from wagyu_sports.utils import get_next_test_number, save_response, test_wagyu_sports
10 | 
11 | __all__ = ['OddsClient', 'get_next_test_number', 'save_response', 'test_wagyu_sports']
12 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/examples/verify_import.py:
--------------------------------------------------------------------------------

```python
 1 | #!/usr/bin/env python3
 2 | """
 3 | Simple verification script to check imports from the Wagyu Sports client.
 4 | 
 5 | This script attempts to import the OddsClient class and create an instance,
 6 | which verifies that the package is installed correctly and can be imported.
 7 | """
 8 | 
 9 | try:
10 |     print("Attempting to import OddsClient...")
11 |     from wagyu_sports import OddsClient
12 |     print("Successfully imported OddsClient!")
13 |     
14 |     # Try creating an instance
15 |     client = OddsClient("test_key")
16 |     print("Successfully created OddsClient instance!")
17 |     
18 | except ImportError as e:
19 |     print(f"Import Error: {e}")
20 |     
21 | except Exception as e:
22 |     print(f"Other Error: {e}")
23 | 
```

--------------------------------------------------------------------------------
/.github/hooks/modules/env-check.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | # Module to check for .env files in commits
 3 | # Prevents accidentally committing environment files with sensitive data
 4 | 
 5 | # Check for .env files in the staged changes
 6 | if git diff --cached --name-only | grep -q "\.env$"; then
 7 |   echo "ERROR: Attempting to commit .env file."
 8 |   echo "These files typically contain sensitive information like API keys."
 9 |   echo "Add them to .gitignore instead and use .env.example as a template."
10 |   echo ""
11 |   echo "Offending files:"
12 |   git diff --cached --name-only | grep "\.env$"
13 |   exit 1
14 | fi
15 | 
16 | # Also check for files that might be .env files with different extensions
17 | if git diff --cached --name-only | grep -q "env\.\|\.env\."; then
18 |   echo "WARNING: Possible environment file detected."
19 |   echo "Please verify these files don't contain sensitive information:"
20 |   git diff --cached --name-only | grep "env\.\|\.env\."
21 |   echo ""
22 |   echo "Continue with commit? (y/n)"
23 |   read -r response
24 |   if [[ "$response" != "y" ]]; then
25 |     exit 1
26 |   fi
27 | fi
28 | 
29 | exit 0
30 | 
```

--------------------------------------------------------------------------------
/.github/hooks/modules/api-key-check.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | # Module to check for API keys in commits
 3 | # Scans for common API key patterns to prevent accidental exposure
 4 | 
 5 | # Define patterns to check for
 6 | # Add more patterns as needed for different types of API keys
 7 | PATTERNS=(
 8 |   # The Odds API key pattern (alphanumeric, typically 32 chars)
 9 |   "ODDS_API_KEY=[a-zA-Z0-9]{32}"
10 |   
11 |   # Generic API key patterns
12 |   "api[_-]key[=\"':= ][a-zA-Z0-9]"
13 |   "apikey[=\"':= ][a-zA-Z0-9]"
14 |   "key[=\"':= ][a-zA-Z0-9]{32}"
15 |   "secret[=\"':= ][a-zA-Z0-9]"
16 |   "password[=\"':= ][a-zA-Z0-9]"
17 |   "token[=\"':= ][a-zA-Z0-9]"
18 | )
19 | 
20 | # Check staged files for API key patterns
21 | for pattern in "${PATTERNS[@]}"; do
22 |   # Use git grep to search staged changes
23 |   matches=$(git diff --cached -U0 | grep -i -E "$pattern" || true)
24 |   
25 |   if [ -n "$matches" ]; then
26 |     echo "ERROR: Potential API key or sensitive data found in commit."
27 |     echo "Pattern matched: $pattern"
28 |     echo ""
29 |     echo "Please remove the sensitive data and try again."
30 |     echo "Consider using environment variables or a secure vault."
31 |     exit 1
32 |   fi
33 | done
34 | 
35 | exit 0
36 | 
```

--------------------------------------------------------------------------------
/.github/workflows/auto-setup-hooks.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Auto Setup Git Hooks
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ main, master ]
 6 |   pull_request:
 7 |     branches: [ main, master ]
 8 |   workflow_dispatch:  # Allows manual triggering
 9 | 
10 | jobs:
11 |   setup-hooks:
12 |     runs-on: ubuntu-latest
13 |     steps:
14 |       - name: Checkout repository
15 |         uses: actions/checkout@v3
16 |         
17 |       - name: Configure Git Hooks
18 |         run: |
19 |           chmod +x .github/scripts/auto-setup.sh
20 |           .github/scripts/auto-setup.sh
21 |           
22 |       - name: Verify Hooks Configuration
23 |         run: |
24 |           echo "Verifying hooks configuration..."
25 |           if [ "$(git config --get core.hooksPath)" = ".github/hooks" ]; then
26 |             echo "✅ Hooks configured successfully"
27 |           else
28 |             echo "❌ Hooks configuration failed"
29 |             exit 1
30 |           fi
31 |           
32 |       - name: Check for .env Files
33 |         run: |
34 |           echo "Checking for .env files..."
35 |           if git ls-files | grep -q "\.env$"; then
36 |             echo "⚠️ Warning: .env files found in repository"
37 |             git ls-files | grep "\.env$"
38 |           else
39 |             echo "✅ No .env files found in repository"
40 |           fi
41 | 
```

--------------------------------------------------------------------------------
/.github/scripts/auto-setup.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | # Auto-setup script for git hooks in local development
 3 | # This script is automatically triggered by GitHub Actions in CI/CD
 4 | # but can also be run manually for local development
 5 | 
 6 | # Exit on error
 7 | set -e
 8 | 
 9 | # Get the root directory of the git repository
10 | REPO_ROOT=$(git rev-parse --show-toplevel)
11 | cd "$REPO_ROOT"
12 | 
13 | echo "🔒 Setting up automatic git hooks..."
14 | 
15 | # Configure git to use hooks from .github/hooks
16 | git config --local core.hooksPath .github/hooks
17 | echo "✓ Configured git to use hooks from .github/hooks"
18 | 
19 | # Make sure all hook scripts are executable
20 | chmod +x .github/hooks/pre-commit
21 | echo "✓ Made pre-commit hook executable"
22 | 
23 | # Make all module scripts executable
24 | MODULE_COUNT=0
25 | for module in .github/hooks/modules/*.sh; do
26 |   if [ -f "$module" ]; then
27 |     chmod +x "$module"
28 |     echo "✓ Made $(basename "$module") executable"
29 |     MODULE_COUNT=$((MODULE_COUNT+1))
30 |   fi
31 | done
32 | 
33 | echo ""
34 | echo "✅ Git hooks configured automatically!"
35 | echo "The following hooks are now active:"
36 | echo "- pre-commit: Prevents committing sensitive information like API keys"
37 | echo ""
38 | echo "Modules configured ($MODULE_COUNT total):"
39 | echo "- env-check.sh: Prevents committing .env files"
40 | echo "- api-key-check.sh: Scans for API key patterns in code"
41 | echo ""
42 | echo "Your repository is now protected against accidental API key commits!"
43 | echo "This configuration will persist across all branches."
44 | 
```

--------------------------------------------------------------------------------
/old/docs/2025-02-15_python_odds_api_fix_postmortem.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Python Odds API Fix Post-Mortem
 2 | 
 3 | ## Issues & Fixes
 4 | 
 5 | ### Issues Identified
 6 | 1. **Deprecation Warning**: Legacy editable install warning during pip installation
 7 | 2. **Import Error**: Package couldn't be imported after installation
 8 | 3. **Package Structure**: Flat structure not following Python packaging conventions
 9 | 
10 | ### Implemented Fixes
11 | 1. Added `pyproject.toml` to fix deprecation warning
12 | 2. Restructured package to use proper nested directory:
13 |    ```
14 |    wagyu_sports/
15 |    ├── pyproject.toml
16 |    ├── setup.py
17 |    └── wagyu_sports/  <-- New subdirectory
18 |        ├── __init__.py
19 |        ├── odds_client.py
20 |        └── utils.py
21 |    ```
22 | 3. Updated root `__init__.py` to import from subdirectory
23 | 
24 | ## Divergence from Original Plan
25 | 
26 | The implementation followed the original plan with two key differences:
27 | 
28 | 1. **Minimal pyproject.toml**: Used minimal configuration rather than comprehensive metadata
29 | 2. **Kept Original Files**: Didn't move test files to a separate tests directory to minimize changes
30 | 
31 | These decisions were made to implement the most critical fixes with minimal changes to the codebase.
32 | 
33 | ## Installation Instructions
34 | 
35 | ```bash
36 | # Clone the repository
37 | git clone <repository-url>
38 | cd wagyu_mcp_hackathon/api_test/wagyu_sports
39 | 
40 | # Install the package
41 | pip install -e .
42 | 
43 | # Set up API key
44 | cp .env.example .env
45 | # Edit .env and add your API key
46 | 
47 | # Verify installation
48 | python run_test.py
49 | ```
50 | 
51 | ## Usage Example
52 | 
53 | ```python
54 | from wagyu_sports import OddsClient
55 | from dotenv import load_dotenv
56 | import os
57 | 
58 | # Load API key
59 | load_dotenv()
60 | api_key = os.getenv("ODDS_API_KEY")
61 | 
62 | # Create client and fetch sports
63 | client = OddsClient(api_key)
64 | sports = client.get_sports()
65 | print(f"Found {len(sports['data'])} sports")
66 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/mcp_server/test_server.py:
--------------------------------------------------------------------------------

```python
 1 | #!/usr/bin/env python3
 2 | """
 3 | Test script for the Wagyu Sports MCP Server.
 4 | 
 5 | This script initializes and runs the MCP server in test mode,
 6 | which uses mock data instead of making real API calls.
 7 | """
 8 | import asyncio
 9 | import os
10 | import sys
11 | import argparse
12 | from pathlib import Path
13 | 
14 | # Import directly from the current directory
15 | from odds_client_server import OddsMcpServer
16 | 
17 | async def main():
18 |     """Run the MCP server."""
19 |     # Parse command line arguments
20 |     parser = argparse.ArgumentParser(description="Wagyu Sports MCP Server")
21 |     parser.add_argument("--test-mode", action="store_true", help="Use mock data instead of real API calls")
22 |     args = parser.parse_args()
23 |     
24 |     # Determine if we should use test mode
25 |     test_mode = args.test_mode
26 |     
27 |     if test_mode:
28 |         print("Starting Wagyu Sports MCP Server in test mode...")
29 |         print("This will use mock data instead of making real API calls.")
30 |     else:
31 |         print("Starting Wagyu Sports MCP Server in live mode...")
32 |         print("This will make real API calls that cost money.")
33 |     print()
34 |     
35 |     # Initialize server
36 |     server = OddsMcpServer(test_mode=test_mode)
37 |     
38 |     # Run the server
39 |     await server.run()
40 | 
41 | if __name__ == "__main__":
42 |     print("=" * 80)
43 |     print("Wagyu Sports MCP Server Test")
44 |     print("=" * 80)
45 |     print()
46 |     print("To test this server with mcp_inspector:")
47 |     print("1. In another terminal, run:")
48 |     print("   npx @modelcontextprotocol/inspector python wagyu_sports/mcp_server/test_server.py")
49 |     print()
50 |     print("2. The inspector will connect to this server and allow you to:")
51 |     print("   - View and test available tools")
52 |     print("   - See the results of tool calls")
53 |     print("   - Monitor server logs")
54 |     print()
55 |     print("3. Try calling these tools:")
56 |     print("   - get_sports")
57 |     print("   - get_odds (with sport='basketball_nba')")
58 |     print("   - get_quota_info")
59 |     print()
60 |     print("=" * 80)
61 |     print()
62 |     
63 |     try:
64 |         asyncio.run(main())
65 |     except KeyboardInterrupt:
66 |         print("\nServer stopped by user.")
67 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/examples/fetch_nba_odds.py:
--------------------------------------------------------------------------------

```python
 1 | #!/usr/bin/env python3
 2 | """
 3 | Example script for fetching NBA odds using the Wagyu Sports client.
 4 | 
 5 | This script demonstrates how to use the Wagyu Sports client to fetch NBA odds
 6 | and save the response to a file.
 7 | """
 8 | import os
 9 | import json
10 | from dotenv import load_dotenv
11 | import requests
12 | 
13 | 
14 | def fetch_nba_odds():
15 |     """
16 |     Fetch NBA odds from the sports data API.
17 |     
18 |     This function:
19 |     1. Loads the API key from environment variables
20 |     2. Makes a direct request to the sports data API for NBA odds
21 |     3. Saves the response to a file
22 |     
23 |     Returns:
24 |         str: Path to the saved file
25 |     """
26 |     # Load environment variables
27 |     load_dotenv(dotenv_path="config/.env")
28 |     
29 |     # Get API key
30 |     api_key = os.getenv("ODDS_API_KEY")
31 |     if not api_key:
32 |         print("Error: ODDS_API_KEY not found in environment variables")
33 |         print("Please copy config/.env.example to config/.env and add your API key: ODDS_API_KEY=your_api_key_here")
34 |         return
35 |     
36 |     try:
37 |         # Get NBA odds
38 |         response = requests.get(
39 |             'https://api.the-odds-api.com/v4/sports/basketball_nba/odds',
40 |             params={
41 |                 'apiKey': api_key,
42 |                 'regions': 'us',
43 |                 'markets': 'h2h,spreads',
44 |                 'oddsFormat': 'american'
45 |             }
46 |         )
47 |         
48 |         # Check for successful response
49 |         response.raise_for_status()
50 |         
51 |         # Prepare output
52 |         output = {
53 |             'odds': response.json(),
54 |             'remainingRequests': response.headers.get('x-requests-remaining')
55 |         }
56 |         
57 |         # Write response to file
58 |         with open('nba_odds.json', 'w') as f:
59 |             json.dump(output, f, indent=2)
60 |         
61 |         print('NBA odds written to nba_odds.json')
62 |         print(f'Remaining requests: {output["remainingRequests"]}')
63 |         
64 |         return 'nba_odds.json'
65 |         
66 |     except requests.exceptions.RequestException as e:
67 |         print(f'Error: {e.response.json() if hasattr(e, "response") else str(e)}')
68 |         return None
69 | 
70 | 
71 | if __name__ == "__main__":
72 |     # Run the example if this file is executed directly
73 |     fetch_nba_odds()
74 | 
```

--------------------------------------------------------------------------------
/old/docs/2025-02-20_task_context.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Python Odds API Fix Task Context
 2 | 
 3 | ## Current State
 4 | 
 5 | - Branch: `fix/python_api`
 6 | - Working Directory: `/Users/brandonbutterwick/Documents/hackathon/wagyu_mcp_hackathon`
 7 | - Project Location: `api_test/wagyu_sports/`
 8 | 
 9 | ## Background Context
10 | 
11 | 1. The Python Odds API package is currently experiencing installation and import issues
12 | 2. A friend's PC was able to run the package successfully, indicating environment-specific problems
13 | 3. The package uses a conda environment named 'sports' for development
14 | 4. Current dependencies are managed through requirements.txt:
15 |    - requests>=2.25.0
16 |    - python-dotenv>=0.15.0
17 | 
18 | ## Known Issues
19 | 
20 | 1. Package fails to import when running `run_test.py`
21 | 2. Legacy editable install warning appears during pip installation
22 | 3. Import paths are not resolving correctly
23 | 4. Package structure needs modernization
24 | 
25 | ## Environment Setup
26 | 
27 | 1. Conda environment 'sports' is being used
28 | 2. Dependencies have been installed in the conda environment
29 | 3. The .env file contains the ODDS_API_KEY for testing
30 | 
31 | ## Recent Changes
32 | 
33 | 1. Created detailed implementation plan in `docs/pip_install_fix_plan.md`
34 | 2. No structural changes have been made yet
35 | 3. Original package structure and files remain intact
36 | 
37 | ## Next Action Items
38 | 
39 | 1. Create new directory structure as outlined in the plan
40 | 2. Implement modern Python packaging with pyproject.toml
41 | 3. Update package configuration in setup.py
42 | 4. Reorganize test files into dedicated test directory
43 | 
44 | ## Important Files
45 | 
46 | - `run_test.py`: Main test script showing import issues
47 | - `odds_client.py`: Core client implementation
48 | - `setup.py`: Package configuration (needs modernization)
49 | - `.env`: Contains API key for testing
50 | - `requirements.txt`: Current dependency specifications
51 | 
52 | ## Testing Notes
53 | 
54 | 1. Test script requires ODDS_API_KEY environment variable
55 | 2. Current test failure is at import level, not API functionality
56 | 3. Package should be tested in both regular and editable install modes
57 | 
58 | ## Additional Context
59 | 
60 | 1. Package is intended to be pip-installable for end users
61 | 2. Need to maintain compatibility with existing API usage patterns
62 | 3. Modern Python packaging practices should be followed
63 | 4. Documentation needs to be updated after fixes
64 | 
65 | This context document should help Roo pick up the task and continue with the implementation phase of the pip installation fixes.
```

--------------------------------------------------------------------------------
/wagyu_sports/tests/test_simple_mcp.py:
--------------------------------------------------------------------------------

```python
 1 | """Simple tests for Wagyu Sports MCP server following the Python MCP SDK example pattern"""
 2 | 
 3 | import json
 4 | import os
 5 | import sys
 6 | import pytest
 7 | from unittest.mock import MagicMock, patch
 8 | 
 9 | # Import directly from the module
10 | from wagyu_sports.mcp_server.odds_client_server import OddsMcpServer
11 | 
12 | @pytest.mark.asyncio
13 | async def test_simple_get_sports():
14 |     """Test the basic functionality of the get_sports tool"""
15 |     # Create an instance of the server in test mode
16 |     server = OddsMcpServer(test_mode=True)
17 |     
18 |     # Test the _get_mock_data method directly
19 |     mock_data = await server._get_mock_data("sports_list.json")
20 |     
21 |     # Parse and verify the response
22 |     data = json.loads(mock_data)
23 |     assert "data" in data
24 |     assert isinstance(data["data"], list)
25 |     assert any(sport["key"] == "basketball_nba" for sport in data["data"])
26 | 
27 | 
28 | @pytest.mark.asyncio
29 | async def test_simple_get_odds():
30 |     """Test the basic functionality of the get_odds tool"""
31 |     # Create an instance of the server in test mode
32 |     server = OddsMcpServer(test_mode=True)
33 |     
34 |     # Test the _get_mock_data method directly for NBA games
35 |     mock_data = await server._get_mock_data("nba_games.json")
36 |     
37 |     # Parse and verify the response
38 |     data = json.loads(mock_data)
39 |     assert "data" in data
40 |     assert isinstance(data["data"], list)
41 |     assert len(data["data"]) > 0
42 |     
43 |     # Check the first game
44 |     first_game = data["data"][0]
45 |     assert "sport_key" in first_game
46 |     assert first_game["sport_key"] == "basketball_nba"
47 |     assert "home_team" in first_game
48 |     assert "away_team" in first_game
49 | 
50 | 
51 | @pytest.mark.asyncio
52 | async def test_simple_get_quota_info():
53 |     """Test the basic functionality of the get_quota_info tool"""
54 |     # Create an instance of the server in test mode
55 |     server = OddsMcpServer(test_mode=True)
56 |     
57 |     # In test mode, get_quota_info returns a fixed response
58 |     # We can construct this manually to test the structure
59 |     expected_data = {
60 |         "remaining_requests": "100",
61 |         "used_requests": "50"
62 |     }
63 |     
64 |     # Create a response similar to what the tool would return
65 |     quota_response = json.dumps(expected_data, indent=2)
66 |     quota_data = json.loads(quota_response)
67 |     
68 |     # Verify the expected structure
69 |     assert "remaining_requests" in quota_data
70 |     assert "used_requests" in quota_data
71 | 
72 | 
73 | if __name__ == "__main__":
74 |     pytest.main(["-xvs", __file__])
75 | 
```

--------------------------------------------------------------------------------
/old/docs/2025-01-20_api_key_security.md:
--------------------------------------------------------------------------------

```markdown
 1 | # API Key Security
 2 | 
 3 | ## Current Status
 4 | 
 5 | After investigating the repository, we've confirmed that:
 6 | 
 7 | 1. The `.env` file containing your API key is **not currently tracked by git**
 8 | 2. The API key `22c87ba01f0fecd6a3acbf114ebcb940` does **not appear in the git history**
 9 | 3. The `.gitignore` file is correctly configured to ignore `.env` files
10 | 
11 | This means your API key is currently secure and has not been exposed in the git history.
12 | 
13 | ## Implemented Security Measures
14 | 
15 | We've created a new branch `feature/git_action_.env` with git hooks to prevent accidentally committing API keys or `.env` files in the future:
16 | 
17 | 1. **Pre-commit hook**: Automatically runs checks before each commit
18 | 2. **Environment file check**: Prevents committing `.env` files
19 | 3. **API key pattern detection**: Scans for API keys in staged changes
20 | 
21 | ## Fully Automated Git Hooks
22 | 
23 | The git hooks are now **fully automated** using GitHub Actions:
24 | 
25 | 1. **No manual setup required** - hooks are configured automatically by CI/CD
26 | 2. **Works across all branches** - protection is consistent everywhere
27 | 3. **Automatic for all developers** - no need to run setup scripts
28 | 
29 | ### For Local Development
30 | 
31 | If you're working locally without GitHub Actions, you can run:
32 | 
33 | ```bash
34 | .github/scripts/auto-setup.sh
35 | ```
36 | 
37 | This will configure Git to use the hooks from `.github/hooks` instead of the default `.git/hooks` directory, making the hooks a default part of the repository.
38 | 
39 | ### CI/CD Integration
40 | 
41 | The GitHub Actions workflow in `.github/workflows/auto-setup-hooks.yml` automatically:
42 | 1. Sets up the hooks for all developers
43 | 2. Verifies the hooks are configured correctly
44 | 3. Checks for any .env files that might have been committed
45 | 
46 | ## Best Practices for API Key Security
47 | 
48 | 1. **Never commit `.env` files**: Store sensitive information in environment variables
49 | 2. **Use `.env.example` files**: Provide templates without real values
50 | 3. **Rotate API keys regularly**: Change keys periodically, especially after suspected exposure
51 | 4. **Use different keys**: Use separate keys for development, testing, and production
52 | 5. **Set up access controls**: Limit who has access to production API keys
53 | 
54 | ## What to Do If You Accidentally Commit an API Key
55 | 
56 | If you accidentally commit an API key:
57 | 
58 | 1. **Rotate the key immediately**: Generate a new key from the service provider
59 | 2. **Remove from git history**: Use tools like `git-filter-repo` to remove sensitive data
60 | 3. **Force push**: Update the remote repository with the cleaned history
61 | 4. **Notify team members**: Ensure everyone pulls the updated history
62 | 
63 | ## Additional Resources
64 | 
65 | - [Git documentation on .gitignore](https://git-scm.com/docs/gitignore)
66 | - [GitHub's guide on removing sensitive data](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository)
67 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/examples/verify_install.py:
--------------------------------------------------------------------------------

```python
 1 | #!/usr/bin/env python3
 2 | """
 3 | Verification script to check the installation and functionality of the Wagyu Sports client.
 4 | 
 5 | This script:
 6 | 1. Checks if the required dependencies are installed
 7 | 2. Verifies that the API key is set in the environment
 8 | 3. Runs a simple test to fetch sports data
 9 | """
10 | import os
11 | import sys
12 | import importlib.util
13 | 
14 | 
15 | def check_dependency(package_name):
16 |     """Check if a Python package is installed."""
17 |     spec = importlib.util.find_spec(package_name)
18 |     if spec is None:
19 |         print(f"Error: {package_name} is not installed.")
20 |         print(f"Please install it using: pip install {package_name}")
21 |         return False
22 |     return True
23 | 
24 | 
25 | def main():
26 |     """Main function to run tests."""
27 |     # Check dependencies
28 |     dependencies = ["requests", "dotenv"]
29 |     all_installed = True
30 |     
31 |     for dep in dependencies:
32 |         if not check_dependency(dep):
33 |             all_installed = False
34 |     
35 |     if not all_installed:
36 |         print("\nPlease install all required dependencies and try again.")
37 |         print("You can install them using: pip install -r build/requirements.txt")
38 |         return
39 |     
40 |     # Now that we know dependencies are installed, import them
41 |     from dotenv import load_dotenv
42 |     
43 |     # Check for API key
44 |     load_dotenv(dotenv_path="config/.env")
45 |     api_key = os.getenv("ODDS_API_KEY")
46 |     
47 |     if not api_key:
48 |         print("Error: ODDS_API_KEY not found in environment variables.")
49 |         print("Please copy config/.env.example to .env and add your API key: ODDS_API_KEY=your_api_key_here")
50 |         return
51 |     
52 |     # Try to import the client
53 |     try:
54 |         from wagyu_sports import OddsClient
55 |         
56 |         # Create client
57 |         client = OddsClient(api_key)
58 |         
59 |         # Test getting sports
60 |         print("Testing API connection by fetching available sports...")
61 |         response = client.get_sports()
62 |         
63 |         # Check response
64 |         if "data" in response and isinstance(response["data"], list):
65 |             sport_count = len(response["data"])
66 |             print(f"Success! Fetched {sport_count} available sports.")
67 |             print(f"Remaining API requests: {response['headers']['x-requests-remaining']}")
68 |             
69 |             # Show a few sports as example
70 |             if sport_count > 0:
71 |                 print("\nSample sports:")
72 |                 for sport in response["data"][:3]:  # Show first 3 sports
73 |                     print(f"- {sport.get('title', 'Unknown')}: {sport.get('key', 'Unknown')}")
74 |             
75 |             print("\nThe Wagyu Sports client is working correctly!")
76 |         else:
77 |             print("Error: Unexpected response format from the API.")
78 |             print("Response:", response)
79 |     
80 |     except ImportError:
81 |         print("Error: Could not import the Wagyu Sports client.")
82 |         print("Make sure the package is installed correctly.")
83 |     
84 |     except Exception as e:
85 |         print(f"Error during API test: {e}")
86 | 
87 | 
88 | if __name__ == "__main__":
89 |     main()
90 | 
```

--------------------------------------------------------------------------------
/old/reagan_planning/mcp_testing_approach.md:
--------------------------------------------------------------------------------

```markdown
 1 | # MCP Testing Approach: Mock Response System
 2 | 
 3 | ## Overview
 4 | 
 5 | This document outlines a testing approach for sports betting MCPs that uses mock data instead of live API calls. This method allows for consistent, repeatable testing without API rate limits or dependencies on current sporting events.
 6 | 
 7 | ## Core Concept
 8 | 
 9 | Create a layered MCP architecture with a base "Odds MCP" that other specialized MCPs can use. This base MCP can operate in either live mode (real API calls) or test mode (predetermined mock responses).
10 | 
11 | ## Implementation Steps
12 | 
13 | ### 1. Create Mock Data Files
14 | 
15 | ```
16 | /mocks/
17 |   sports_list.json         # Available sports
18 |   nba_games.json           # List of NBA games
19 |   game_odds_caesars.json   # Single game odds from default book
20 |   game_odds_all_books.json # Comparison across books
21 |   futures_odds.json        # Championship futures odds
22 | ```
23 | 
24 | ### 2. Build a Test-Ready Base MCP
25 | 
26 | ```python
27 | # Base MCP with test/live mode toggle
28 | class OddsMCP:
29 |     def __init__(self, api_key=None, test_mode=False):
30 |         self.api_key = api_key
31 |         self.test_mode = test_mode
32 |         
33 |     def get_data(self, endpoint, params=None):
34 |         if self.test_mode:
35 |             # Return appropriate mock based on endpoint and params
36 |             return self._get_mock_response(endpoint, params)
37 |         else:
38 |             # Make actual API call
39 |             return self._call_api(endpoint, params)
40 | ```
41 | 
42 | ### 3. Create Specialized MCPs Using the Base
43 | 
44 | ```python
45 | # Example of a specialized MCP
46 | class GameBrowserMCP:
47 |     def __init__(self, odds_mcp):
48 |         self.odds_mcp = odds_mcp
49 |         
50 |     def get_available_sports(self):
51 |         # Uses the base MCP, which could be in test mode
52 |         return self.odds_mcp.get_data("sports")
53 |         
54 |     def get_games_for_sport(self, sport):
55 |         return self.odds_mcp.get_data("games", {"sport": sport})
56 | ```
57 | 
58 | ## Benefits
59 | 
60 | 1. **Controlled Testing Environment**: Predictable data for each test scenario
61 | 2. **Fast Execution**: No network delays or API rate limits
62 | 3. **Comprehensive Coverage**: Test edge cases and rare situations
63 | 4. **Isolated Components**: Test each MCP layer independently
64 | 5. **Repeatable Results**: Tests produce consistent output
65 | 
66 | ## Mock Data Example
67 | 
68 | ```json
69 | // Example mock for a game odds response
70 | {
71 |   "game": {
72 |     "home_team": "Lakers",
73 |     "away_team": "Grizzlies",
74 |     "commence_time": "2025-03-02T19:30:00Z"
75 |   },
76 |   "odds": {
77 |     "caesars": {
78 |       "spread": {"home": -4.5, "away": 4.5, "odds": -110},
79 |       "moneyline": {"home": -190, "away": 160},
80 |       "totals": {"over": 224.5, "under": 224.5, "odds": -110}
81 |     }
82 |   }
83 | }
84 | ```
85 | 
86 | ## Test Flow
87 | 
88 | 1. Initialize test versions of MCPs with `test_mode=True`
89 | 2. Run through user scenarios from the test_scenarios.md document
90 | 3. Compare MCP outputs to expected responses
91 | 4. Toggle to live mode for final verification with real data
92 | 
93 | This approach provides a stable foundation for development while keeping the option to test against live data when needed.
94 | 
```

--------------------------------------------------------------------------------
/docs/reagan_planning/transcript_insights.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Sports Betting AI - Transcript Insights
 2 | 
 3 | This document summarizes key insights from the conversation with Reagan about sports betting user needs and preferences.
 4 | 
 5 | ## 1. User Stories / Scenarios
 6 | 
 7 | ### Scenario 1: The Value Shopper
 8 | **User Need:** "I want to place a bet on Loyola Marymount at the highest possible odds"  
 9 | **Action:** Compares odds across multiple sportsbooks for a specific selection  
10 | **Output Example:** "DraftKings offers Loyola Marymount at +925, while FanDuel has them at +900"
11 | 
12 | ### Scenario 2: The Casual Game Night Better
13 | **User Need:** "I'm watching the Lakers game with friends and want to place a quick bet"  
14 | **Action:** Looks up basic odds for a specific game  
15 | **Output Example:** "Lakers vs. Grizzlies: Lakers -4.5, O/U 224.5, Lakers ML -190"
16 | 
17 | ### Scenario 3: The Trend Spotter
18 | **User Need:** "I want to see if there's unusual line movement on any games tonight"  
19 | **Action:** Looks for significant recent changes in betting lines  
20 | **Output Example:** "The White Sox line has moved from +300 to +375 in the last hour despite 60% of bets being placed on them"
21 | 
22 | ## 2. Data Points to Track
23 | 
24 | | Data Category | Priority | Examples |
25 | |---------------|----------|----------|
26 | | Basic Odds | High | Spread, Money Line, Over/Under |
27 | | Book Comparison | High | Same bet across multiple sportsbooks |
28 | | Line Movement | Medium | Changes from opening line to current |
29 | | Bet Distribution | Medium | % of bets vs % of money on each side |
30 | | Futures | Medium | Long-term bets with higher variation across books |
31 | 
32 | ## 3. User Experience Flow
33 | 
34 | 1. **Initial Query**
35 |    - User asks: "Show me the line for Lakers-Grizzlies"
36 |    
37 | 2. **Primary Response**
38 |    - Default sportsbook odds (Caesar's suggested)
39 |    - Standard format: Spread, O/U, Money Line
40 |    
41 | 3. **Secondary Options**
42 |    - "Would you like to see odds from other sportsbooks?"
43 |    - "Would you like to see betting trends for this game?"
44 |    
45 | 4. **Advanced Data (On Request)**
46 |    - Line movement information
47 |    - Bet distribution (money vs. bets)
48 | 
49 | ## 4. Implementation Priorities
50 | 
51 | 1. Basic odds retrieval for major US sports (single sportsbook)
52 | 2. Multiple sportsbook comparison for same game/bet
53 | 3. Format optimization for AI chat interface
54 | 4. Line movement indicators (if time permits)
55 | 5. Bet distribution data (if available in your API)
56 | 
57 | ## 5. Key Insights from Reagan
58 | 
59 | - **Futures bets** have the most variation between sportsbooks and offer the best opportunity for value shopping
60 | - **Line movement** is a key indicator that bettors look for to identify potential value
61 | - **Bet distribution** (% of money vs. % of bets) can signal "sharp" money and is valuable information
62 | - **Layered information** is important - start with basic odds, then offer more detailed information
63 | - **User preferences** for which sportsbooks to display is important
64 | - **Anomalies** in odds or betting patterns can either attract or repel bettors depending on their strategy
65 | - **Default display** should be simple (spread, money line, over/under) with options to see more
66 | 
```

--------------------------------------------------------------------------------
/old/docs/2025-02-10_pip_install_fix_plan.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Plan for Making Python Odds API Properly Pip-Installable
  2 | 
  3 | After analyzing the current state of the Python Odds API package, I've identified several issues that prevent it from being properly installed and imported. Here's a comprehensive plan to fix these issues:
  4 | 
  5 | ## 1. Diagnose Current Issues
  6 | 
  7 | Currently, the package can be installed with `pip install -e .` but fails with an import error when running `run_test.py`. The main issues appear to be:
  8 | 
  9 | - Package structure is not following modern Python packaging best practices
 10 | - The installation process is working but the import path is not resolving correctly
 11 | - The setup.py file needs modernization
 12 | - There's a deprecation warning about legacy editable installs
 13 | 
 14 | ## 2. Package Restructuring
 15 | 
 16 | ### 2.1. Create a Modern Package Structure
 17 | 
 18 | ```
 19 | api_test/wagyu_sports/
 20 | ├── pyproject.toml         # New file for modern Python packaging
 21 | ├── setup.py               # Updated version
 22 | ├── README.md             # Existing file
 23 | ├── requirements.txt      # Existing file
 24 | ├── wagyu_sports/      # Package directory (new or renamed)
 25 | │   ├── __init__.py       # Updated to expose the correct modules
 26 | │   ├── odds_client.py    # Main module
 27 | │   └── utils.py          # Utilities module
 28 | └── tests/               # Separate test directory (new)
 29 |     ├── __init__.py
 30 |     ├── run_test.py       # Moved from root
 31 |     └── test_odds_client.py # Existing test
 32 | ```
 33 | 
 34 | ## 3. Update Package Files
 35 | 
 36 | ### 3.1. Create a Modern pyproject.toml
 37 | 
 38 | Create a new `pyproject.toml` file to use modern Python packaging tools (PEP 517/518) with:
 39 | - Build system specification
 40 | - Project metadata
 41 | - Dependencies
 42 | - Development dependencies
 43 | 
 44 | ### 3.2. Update setup.py
 45 | 
 46 | Modernize the existing setup.py:
 47 | - Ensure package discovery works correctly
 48 | - Update metadata
 49 | - Fix URL and author information
 50 | - Make sure dependencies match requirements.txt
 51 | 
 52 | ### 3.3. Update __init__.py
 53 | 
 54 | Ensure the package's `__init__.py` correctly exposes the intended classes and functions.
 55 | 
 56 | ## 4. Fix Import Paths
 57 | 
 58 | ### 4.1. Update Import Statements
 59 | 
 60 | Ensure all import statements in the package are consistent with the new structure.
 61 | 
 62 | ### 4.2. Update Test Scripts
 63 | 
 64 | Modify test scripts to use the correct import paths.
 65 | 
 66 | ## 5. Testing Plan
 67 | 
 68 | ### 5.1. Test Installation
 69 | 
 70 | Test the package installation in different scenarios:
 71 | - Regular install: `pip install .`
 72 | - Editable install: `pip install -e .`
 73 | - Install from source distribution: `pip install dist/*.tar.gz`
 74 | 
 75 | ### 5.2. Test Imports
 76 | 
 77 | Verify imports work correctly:
 78 | - In different Python environments
 79 | - From different directories
 80 | - With different import statements
 81 | 
 82 | ### 5.3. Functional Testing
 83 | 
 84 | Run the test scripts to ensure the client functions correctly:
 85 | - Basic API functionality
 86 | - Error handling
 87 | - Edge cases
 88 | 
 89 | ## 6. Documentation Updates
 90 | 
 91 | ### 6.1. Update README
 92 | 
 93 | Update installation and usage instructions in the README.md file.
 94 | 
 95 | ### 6.2. Add Examples
 96 | 
 97 | Provide clear examples of how to install and use the package.
 98 | 
 99 | ## 7. Cleanup
100 | 
101 | Remove any temporary files or old structures that are no longer needed.
102 | 
103 | ## Implementation Timeline
104 | 
105 | 1. Package restructuring (30 minutes)
106 | 2. Update configuration files (20 minutes)
107 | 3. Fix import paths (15 minutes)
108 | 4. Testing (20 minutes)
109 | 5. Documentation updates (15 minutes)
110 | 
111 | ## Next Steps
112 | 
113 | 1. Create the new directory structure
114 | 2. Create pyproject.toml
115 | 3. Update setup.py with modern configuration
116 | 4. Move and update test files
117 | 5. Test the installation process
```

--------------------------------------------------------------------------------
/wagyu_sports/examples/example.py:
--------------------------------------------------------------------------------

```python
 1 | #!/usr/bin/env python3
 2 | """
 3 | Example script demonstrating how to use the Wagyu Sports client.
 4 | """
 5 | import os
 6 | from dotenv import load_dotenv
 7 | from wagyu_sports import OddsClient
 8 | 
 9 | 
10 | def main():
11 |     """
12 |     Main function demonstrating the use of the Wagyu Sports client.
13 |     """
14 |     # Load environment variables
15 |     load_dotenv(dotenv_path="config/.env")
16 |     
17 |     # Get API key
18 |     api_key = os.getenv("ODDS_API_KEY")
19 |     if not api_key:
20 |         print("Error: ODDS_API_KEY not found in environment variables")
21 |         print("Please copy config/.env.example to config/.env and add your API key: ODDS_API_KEY=your_api_key_here")
22 |         return
23 |     
24 |     # Create client
25 |     client = OddsClient(api_key)
26 |     
27 |     # Get available sports
28 |     try:
29 |         print("Fetching available sports...")
30 |         sports_response = client.get_sports()
31 |         
32 |         print(f"\nAvailable sports: {len(sports_response['data'])}")
33 |         print("Sample sports:")
34 |         for sport in sports_response['data'][:5]:  # Show first 5 sports
35 |             print(f"- {sport.get('title', 'Unknown')}: {sport.get('key', 'Unknown')}")
36 |         
37 |         print(f"\nRemaining requests: {sports_response['headers']['x-requests-remaining']}")
38 |         
39 |         # Get a sport key for the next request
40 |         sport_key = None
41 |         for sport in sports_response['data']:
42 |             if sport.get('title') == 'NBA':
43 |                 sport_key = sport.get('key')
44 |                 break
45 |         
46 |         if not sport_key:
47 |             # If NBA is not found, use the first available sport
48 |             sport_key = sports_response['data'][0].get('key') if sports_response['data'] else None
49 |         
50 |         if not sport_key:
51 |             print("\nNo sports available to fetch odds")
52 |             return
53 |         
54 |         # Get odds for the selected sport
55 |         print(f"\nFetching odds for {sport_key}...")
56 |         odds_options = {
57 |             "regions": "us",
58 |             "markets": "h2h",
59 |             "oddsFormat": "decimal"
60 |         }
61 |         odds_response = client.get_odds(sport_key, odds_options)
62 |         
63 |         games_count = len(odds_response['data'])
64 |         print(f"\nFetched odds for {games_count} games")
65 |         
66 |         if games_count > 0:
67 |             print("\nSample games:")
68 |             for game in odds_response['data'][:3]:  # Show first 3 games
69 |                 home_team = game.get('home_team', 'Unknown')
70 |                 away_team = game.get('away_team', 'Unknown')
71 |                 commence_time = game.get('commence_time', 'Unknown')
72 |                 print(f"- {away_team} @ {home_team} (Start: {commence_time})")
73 |                 
74 |                 # Print sample odds from first bookmaker if available
75 |                 if game.get('bookmakers') and len(game.get('bookmakers')) > 0:
76 |                     bookmaker = game.get('bookmakers')[0]
77 |                     print(f"  Odds from {bookmaker.get('title', 'Unknown')}:")
78 |                     
79 |                     for market in bookmaker.get('markets', []):
80 |                         if market.get('key') == 'h2h':
81 |                             print("  Moneyline:")
82 |                             for outcome in market.get('outcomes', []):
83 |                                 print(f"    {outcome.get('name', 'Unknown')}: {outcome.get('price', 'Unknown')}")
84 |         
85 |         print(f"\nRemaining requests: {odds_response['headers']['x-requests-remaining']}")
86 |         
87 |     except Exception as e:
88 |         print(f"Error: {e}")
89 | 
90 | 
91 | if __name__ == "__main__":
92 |     main()
93 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/mcp_server/capture_live_responses.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Script to capture live API responses from the Odds API.
  4 | 
  5 | This script makes live API calls and saves the responses to JSON files
  6 | in the mocks_live directory. These captures can be used for creating
  7 | updated mock data for testing.
  8 | 
  9 | IMPORTANT: Each live API call costs money. Use sparingly.
 10 | """
 11 | import os
 12 | import sys
 13 | import json
 14 | import asyncio
 15 | from pathlib import Path
 16 | from datetime import datetime
 17 | 
 18 | # Add parent directory to path to import from wagyu_sports
 19 | sys.path.insert(0, str(Path(__file__).parent.parent.parent))
 20 | 
 21 | from wagyu_sports.odds_client import OddsClient
 22 | 
 23 | async def capture_response(client, method_name, filename, **kwargs):
 24 |     """
 25 |     Capture a response from the API and save it to a file.
 26 |     
 27 |     Args:
 28 |         client: The OddsClient instance
 29 |         method_name: The name of the method to call
 30 |         filename: The name of the file to save the response to
 31 |         **kwargs: Arguments to pass to the method
 32 |     """
 33 |     print(f"Calling {method_name} with {kwargs}...")
 34 |     
 35 |     # Call the method
 36 |     method = getattr(client, method_name)
 37 |     result = method(**kwargs)
 38 |     
 39 |     # Add metadata
 40 |     response = {
 41 |         "_metadata": {
 42 |             "captured_at": datetime.now().isoformat(),
 43 |             "method": method_name,
 44 |             "parameters": kwargs
 45 |         },
 46 |         "data": result
 47 |     }
 48 |     
 49 |     # Save to file
 50 |     output_path = Path(__file__).parent / "mocks_live" / filename
 51 |     with open(output_path, "w") as f:
 52 |         json.dump(response, f, indent=2)
 53 |     
 54 |     print(f"Response saved to {output_path}")
 55 |     return result
 56 | 
 57 | async def main():
 58 |     """Run the capture script."""
 59 |     # Get API key from environment
 60 |     api_key = os.environ.get("ODDS_API_KEY")
 61 |     if not api_key:
 62 |         print("Error: ODDS_API_KEY environment variable is not set")
 63 |         sys.exit(1)
 64 |     
 65 |     print("=" * 80)
 66 |     print("Wagyu Sports Live API Response Capture")
 67 |     print("=" * 80)
 68 |     print()
 69 |     print("WARNING: This script makes live API calls that cost money.")
 70 |     print("Only run this script when necessary.")
 71 |     print()
 72 |     
 73 |     # Initialize client
 74 |     client = OddsClient(api_key)
 75 |     
 76 |     # Capture sports list
 77 |     await capture_response(
 78 |         client, 
 79 |         "get_sports", 
 80 |         "sports_list_live.json",
 81 |         all_sports=True
 82 |     )
 83 |     
 84 |     # Capture NBA odds
 85 |     await capture_response(
 86 |         client, 
 87 |         "get_odds", 
 88 |         "nba_games_live.json",
 89 |         sport="basketball_nba",
 90 |         options={
 91 |             "regions": "us",
 92 |             "markets": "h2h,spreads"
 93 |         }
 94 |     )
 95 |     
 96 |     # Capture soccer odds
 97 |     await capture_response(
 98 |         client, 
 99 |         "get_odds", 
100 |         "soccer_epl_live.json",
101 |         sport="soccer_epl",
102 |         options={
103 |             "regions": "us,uk",
104 |             "markets": "h2h,spreads,totals"
105 |         }
106 |     )
107 |     
108 |     # Print quota information
109 |     print("\nQuota Information:")
110 |     print(f"Remaining requests: {client.remaining_requests}")
111 |     print(f"Used requests: {client.used_requests}")
112 |     
113 |     # Save quota information
114 |     quota_info = {
115 |         "_metadata": {
116 |             "captured_at": datetime.now().isoformat()
117 |         },
118 |         "remaining_requests": client.remaining_requests,
119 |         "used_requests": client.used_requests
120 |     }
121 |     
122 |     quota_path = Path(__file__).parent / "mocks_live" / "quota_info_live.json"
123 |     with open(quota_path, "w") as f:
124 |         json.dump(quota_info, f, indent=2)
125 |     
126 |     print(f"Quota information saved to {quota_path}")
127 |     
128 |     print("\nCapture complete!")
129 | 
130 | if __name__ == "__main__":
131 |     try:
132 |         asyncio.run(main())
133 |     except KeyboardInterrupt:
134 |         print("\nCapture stopped by user.")
135 |     except Exception as e:
136 |         print(f"\nError: {e}")
137 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/mcp_server/odds_client.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Wagyu Sports Client Module
  4 | 
  5 | This module provides a client for interacting with sports betting data APIs.
  6 | """
  7 | import requests
  8 | from typing import Dict, List, Optional, Any, Union
  9 | 
 10 | 
 11 | class OddsClient:
 12 |     """
 13 |     Client for sports betting data.
 14 |     
 15 |     This class provides methods for fetching sports betting data including
 16 |     available sports and odds for specific sports.
 17 |     """
 18 |     
 19 |     BASE_URL = "https://api.the-odds-api.com/v4"
 20 |     
 21 |     def __init__(self, api_key: str):
 22 |         """
 23 |         Initialize the Wagyu Sports client.
 24 |         
 25 |         Args:
 26 |             api_key (str): API key for authentication with The Odds API
 27 |         """
 28 |         self.api_key = api_key
 29 |         self.remaining_requests = None
 30 |         self.used_requests = None
 31 |     
 32 |     def get_sports(self, all_sports: bool = False) -> Dict[str, Any]:
 33 |         """
 34 |         Get a list of available sports.
 35 |         
 36 |         Args:
 37 |             all_sports (bool, optional): Include out-of-season sports. Defaults to False.
 38 |             
 39 |         Returns:
 40 |             Dict[str, Any]: Response containing available sports data
 41 |             
 42 |         Raises:
 43 |             requests.exceptions.RequestException: If the request fails
 44 |         """
 45 |         params = {"apiKey": self.api_key}
 46 |         if all_sports:
 47 |             params["all"] = "true"
 48 |             
 49 |         return self.make_request("/sports", params)
 50 |     
 51 |     def get_odds(self, sport: str, options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
 52 |         """
 53 |         Get odds for a specific sport.
 54 |         
 55 |         Args:
 56 |             sport (str): Sport key (e.g., 'basketball_nba')
 57 |             options (Dict[str, Any], optional): Additional options for the request. Defaults to None.
 58 |                 Possible options include:
 59 |                 - regions: Comma-separated list of regions (e.g., 'us,uk')
 60 |                 - markets: Comma-separated list of markets (e.g., 'h2h,spreads')
 61 |                 - oddsFormat: Format for odds ('decimal' or 'american')
 62 |                 - dateFormat: Format for dates ('unix' or 'iso')
 63 |                 
 64 |         Returns:
 65 |             Dict[str, Any]: Response containing odds data
 66 |             
 67 |         Raises:
 68 |             requests.exceptions.RequestException: If the request fails
 69 |         """
 70 |         endpoint = f"/sports/{sport}/odds"
 71 |         params = {"apiKey": self.api_key}
 72 |         
 73 |         if options:
 74 |             params.update(options)
 75 |             
 76 |         return self.make_request(endpoint, params)
 77 |     
 78 |     def make_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
 79 |         """
 80 |         Make a request to the sports data API.
 81 |         
 82 |         Args:
 83 |             endpoint (str): API endpoint (e.g., '/sports')
 84 |             params (Dict[str, Any], optional): Query parameters. Defaults to None.
 85 |             
 86 |         Returns:
 87 |             Dict[str, Any]: Response data
 88 |             
 89 |         Raises:
 90 |             requests.exceptions.RequestException: If the request fails
 91 |         """
 92 |         url = f"{self.BASE_URL}{endpoint}"
 93 |         response = requests.get(url, params=params)
 94 |         
 95 |         # Store quota information from headers
 96 |         if 'x-requests-remaining' in response.headers:
 97 |             self.remaining_requests = response.headers['x-requests-remaining']
 98 |         if 'x-requests-used' in response.headers:
 99 |             self.used_requests = response.headers['x-requests-used']
100 |         
101 |         # Raise exception for error status codes
102 |         response.raise_for_status()
103 |         
104 |         # Return JSON response
105 |         return {
106 |             "data": response.json(),
107 |             "headers": {
108 |                 "x-requests-remaining": self.remaining_requests,
109 |                 "x-requests-used": self.used_requests
110 |             }
111 |         }
112 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/odds_client.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Wagyu Sports Client Module
  4 | 
  5 | This module provides a client for interacting with sports betting data APIs.
  6 | """
  7 | import requests
  8 | from typing import Dict, List, Optional, Any, Union
  9 | 
 10 | 
 11 | class OddsClient:
 12 |     """
 13 |     Client for sports betting data.
 14 |     
 15 |     This class provides methods for fetching sports betting data including
 16 |     available sports and odds for specific sports.
 17 |     """
 18 |     
 19 |     BASE_URL = "https://api.the-odds-api.com/v4"
 20 |     
 21 |     def __init__(self, api_key: str):
 22 |         """
 23 |         Initialize the Wagyu Sports client.
 24 |         
 25 |         Args:
 26 |             api_key (str): API key for authentication with The Odds API
 27 |         """
 28 |         self.api_key = api_key
 29 |         self.remaining_requests = None
 30 |         self.used_requests = None
 31 |     
 32 |     def get_sports(self, all_sports: bool = False) -> Dict[str, Any]:
 33 |         """
 34 |         Get a list of available sports.
 35 |         
 36 |         Args:
 37 |             all_sports (bool, optional): Include out-of-season sports. Defaults to False.
 38 |             
 39 |         Returns:
 40 |             Dict[str, Any]: Response containing available sports data
 41 |             
 42 |         Raises:
 43 |             requests.exceptions.RequestException: If the request fails
 44 |         """
 45 |         params = {"apiKey": self.api_key}
 46 |         if all_sports:
 47 |             params["all"] = "true"
 48 |             
 49 |         return self.make_request("/sports", params)
 50 |     
 51 |     def get_odds(self, sport: str, options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
 52 |         """
 53 |         Get odds for a specific sport.
 54 |         
 55 |         Args:
 56 |             sport (str): Sport key (e.g., 'basketball_nba')
 57 |             options (Dict[str, Any], optional): Additional options for the request. Defaults to None.
 58 |                 Possible options include:
 59 |                 - regions: Comma-separated list of regions (e.g., 'us,uk')
 60 |                 - markets: Comma-separated list of markets (e.g., 'h2h,spreads')
 61 |                 - oddsFormat: Format for odds ('decimal' or 'american')
 62 |                 - dateFormat: Format for dates ('unix' or 'iso')
 63 |                 
 64 |         Returns:
 65 |             Dict[str, Any]: Response containing odds data
 66 |             
 67 |         Raises:
 68 |             requests.exceptions.RequestException: If the request fails
 69 |         """
 70 |         endpoint = f"/sports/{sport}/odds"
 71 |         params = {"apiKey": self.api_key}
 72 |         
 73 |         if options:
 74 |             params.update(options)
 75 |             
 76 |         return self.make_request(endpoint, params)
 77 |     
 78 |     def make_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
 79 |         """
 80 |         Make a request to the sports data API.
 81 |         
 82 |         Args:
 83 |             endpoint (str): API endpoint (e.g., '/sports')
 84 |             params (Dict[str, Any], optional): Query parameters. Defaults to None.
 85 |             
 86 |         Returns:
 87 |             Dict[str, Any]: Response data
 88 |             
 89 |         Raises:
 90 |             requests.exceptions.RequestException: If the request fails
 91 |         """
 92 |         url = f"{self.BASE_URL}{endpoint}"
 93 |         response = requests.get(url, params=params)
 94 |         
 95 |         # Store quota information from headers
 96 |         if 'x-requests-remaining' in response.headers:
 97 |             self.remaining_requests = response.headers['x-requests-remaining']
 98 |         if 'x-requests-used' in response.headers:
 99 |             self.used_requests = response.headers['x-requests-used']
100 |         
101 |         # Raise exception for error status codes
102 |         response.raise_for_status()
103 |         
104 |         # Return JSON response
105 |         return {
106 |             "data": response.json(),
107 |             "headers": {
108 |                 "x-requests-remaining": self.remaining_requests,
109 |                 "x-requests-used": self.used_requests
110 |             }
111 |         }
112 | 
```

--------------------------------------------------------------------------------
/old/espn_nonbetting_api/espn_api_endpoints.md:
--------------------------------------------------------------------------------

```markdown
  1 | # ESPN's hidden API endpoints
  2 | 
  3 | ## Football
  4 | 
  5 | ### College Football 
  6 | 
  7 | **Latest News**: http://site.api.espn.com/apis/site/v2/sports/football/college-football/news
  8 | 
  9 | **Latest Scores**: http://site.api.espn.com/apis/site/v2/sports/football/college-football/scoreboard
 10 | 
 11 | - query params:
 12 | 
 13 |    - calendar: 'blacklist'
 14 |    - dates: any date in YYYYMMDD
 15 |    
 16 | **Game Information**: http://site.api.espn.com/apis/site/v2/sports/football/college-football/summary?event=:gameId
 17 | 
 18 | - params:
 19 | 
 20 |    - gameId: identifier of some game (EX: 400934572 for 2017 Army vs Navy)
 21 |         
 22 | **Team Information**: http://site.api.espn.com/apis/site/v2/sports/football/college-football/teams/:team
 23 | 
 24 | - params: 
 25 | 
 26 |    - team: some team abbreviation (EX: 'all' for Allegheny, 'gt' for Georgia Tech, 'wisconsin' for Wisconsin)
 27 |    
 28 | **Rankings**: http://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings
 29 | 
 30 | ### NFL
 31 | 
 32 | **Scores**: http://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard
 33 | 
 34 | **News**: http://site.api.espn.com/apis/site/v2/sports/football/nfl/news
 35 | 
 36 | **All Teams**: http://site.api.espn.com/apis/site/v2/sports/football/nfl/teams
 37 | 
 38 | **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/football/nfl/teams/:team
 39 | 
 40 | 
 41 | ## Baseball
 42 | 
 43 | ### MLB
 44 | 
 45 | **Scores**: http://site.api.espn.com/apis/site/v2/sports/baseball/mlb/scoreboard
 46 | 
 47 | **News**: http://site.api.espn.com/apis/site/v2/sports/baseball/mlb/news
 48 | 
 49 | **All Teams**: http://site.api.espn.com/apis/site/v2/sports/baseball/mlb/teams
 50 | 
 51 | **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/baseball/mlb/teams/:team
 52 | 
 53 | ### College Baseball
 54 | 
 55 | **Scores**: https://site.api.espn.com/apis/site/v2/sports/baseball/college-baseball/scoreboard
 56 | 
 57 | ## Hockey
 58 | 
 59 | **Scores**: http://site.api.espn.com/apis/site/v2/sports/hockey/nhl/scoreboard
 60 | 
 61 | **News**: http://site.api.espn.com/apis/site/v2/sports/hockey/nhl/news
 62 | 
 63 | **All Teams**: http://site.api.espn.com/apis/site/v2/sports/hockey/nhl/teams
 64 | 
 65 | **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/hockey/nhl/teams/:team
 66 | 
 67 | 
 68 | ## Basketball
 69 | 
 70 | ### NBA
 71 | 
 72 | **Scores**: http://site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboard
 73 | 
 74 | **News**: http://site.api.espn.com/apis/site/v2/sports/basketball/nba/news
 75 | 
 76 | **All Teams**: http://site.api.espn.com/apis/site/v2/sports/basketball/nba/teams
 77 | 
 78 | **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/basketball/nba/teams/:team
 79 | 
 80 | 
 81 | ### WNBA
 82 | 
 83 | **Scores**: http://site.api.espn.com/apis/site/v2/sports/basketball/wnba/scoreboard
 84 | 
 85 | **News**: http://site.api.espn.com/apis/site/v2/sports/basketball/wnba/news
 86 | 
 87 | **All Teams**: http://site.api.espn.com/apis/site/v2/sports/basketball/wnba/teams
 88 | 
 89 | **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/basketball/wnba/teams/:team
 90 | 
 91 | 
 92 | ### Women's College Basketball
 93 | 
 94 | **Scores**: http://site.api.espn.com/apis/site/v2/sports/basketball/womens-college-basketball/scoreboard
 95 | 
 96 | **News**: http://site.api.espn.com/apis/site/v2/sports/basketball/womens-college-basketball/news
 97 | 
 98 | **All Teams**: http://site.api.espn.com/apis/site/v2/sports/basketball/womens-college-basketball/teams
 99 | 
100 | **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/basketball/womens-college-basketball/teams/:team
101 | 
102 | 
103 | ### Men's College Basketball
104 | 
105 | **Scores**: http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard
106 | 
107 | **News**: http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/news
108 | 
109 | **All Teams**: http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/teams
110 | 
111 | **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/teams/:team
112 | 
113 | 
114 | 
115 | ## Soccer
116 | 
117 | **Scores**: http://site.api.espn.com/apis/site/v2/sports/soccer/:league/scoreboard
118 | 
119 | - params:
120 | 
121 |    - league: some league abbreviation (EX: 'eng.1' for EPL, 'usa.1' for MLS) 
122 |    
123 | **Latest News**: http://site.api.espn.com/apis/site/v2/sports/soccer/:league/news
124 | 
125 | **List of Team Information**: http://site.api.espn.com/apis/site/v2/sports/soccer/:league/teams
126 | 
127 | 
128 | Will update with more information as I find more...
129 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/utils.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Utility functions for working with the Wagyu Sports client.
  4 | """
  5 | import os
  6 | import json
  7 | import glob
  8 | from typing import Dict, Any, Optional, Tuple
  9 | from pathlib import Path
 10 | from dotenv import load_dotenv
 11 | 
 12 | from .odds_client import OddsClient
 13 | 
 14 | 
 15 | def get_next_test_number() -> int:
 16 |     """
 17 |     Get the next sequential test number for the output directory.
 18 |     
 19 |     This function looks at existing test directories and returns the next number in sequence.
 20 |     
 21 |     Returns:
 22 |         int: Next test number
 23 |     """
 24 |     # Define the base directory for test outputs
 25 |     base_dir = os.path.join(os.getcwd(), "test_outputs")
 26 |     
 27 |     # Ensure the directory exists
 28 |     os.makedirs(base_dir, exist_ok=True)
 29 |     
 30 |     # Find all test directories
 31 |     test_dirs = glob.glob(os.path.join(base_dir, "test*"))
 32 |     
 33 |     if not test_dirs:
 34 |         return 1
 35 |     
 36 |     # Extract numbers from directory names
 37 |     numbers = []
 38 |     for dir_path in test_dirs:
 39 |         dir_name = os.path.basename(dir_path)
 40 |         try:
 41 |             # Extract number from "test{number}"
 42 |             num = int(dir_name.replace("test", ""))
 43 |             numbers.append(num)
 44 |         except ValueError:
 45 |             continue
 46 |     
 47 |     # Return next number in sequence
 48 |     return max(numbers) + 1 if numbers else 1
 49 | 
 50 | 
 51 | def save_response(filename: str, data: Dict[str, Any], test_number: Optional[int] = None) -> str:
 52 |     """
 53 |     Save API response to a JSON file.
 54 |     
 55 |     Args:
 56 |         filename (str): Name of the file to save
 57 |         data (Dict[str, Any]): Data to save
 58 |         test_number (int, optional): Test number for directory. If None, uses next available.
 59 |         
 60 |     Returns:
 61 |         str: Path to the saved file
 62 |     """
 63 |     # Get test number if not provided
 64 |     if test_number is None:
 65 |         test_number = get_next_test_number()
 66 |     
 67 |     # Define directory path
 68 |     dir_path = os.path.join(os.getcwd(), "test_outputs", f"test{test_number}")
 69 |     
 70 |     # Ensure directory exists
 71 |     os.makedirs(dir_path, exist_ok=True)
 72 |     
 73 |     # Define file path
 74 |     file_path = os.path.join(dir_path, filename)
 75 |     
 76 |     # Save data to file
 77 |     with open(file_path, 'w') as f:
 78 |         json.dump(data, f, indent=2)
 79 |     
 80 |     return file_path
 81 | 
 82 | 
 83 | def test_wagyu_sports() -> Tuple[str, str]:
 84 |     """
 85 |     Example function that demonstrates full API workflow.
 86 |     
 87 |     This function:
 88 |     1. Loads the API key from environment variables
 89 |     2. Creates an OddsClient instance
 90 |     3. Fetches available sports
 91 |     4. Fetches NBA odds
 92 |     5. Saves responses to files
 93 |     
 94 |     Returns:
 95 |         Tuple[str, str]: Paths to the saved files (sports, odds)
 96 |     """
 97 |     # Load environment variables
 98 |     load_dotenv(dotenv_path="config/.env")
 99 |     
100 |     # Get API key
101 |     api_key = os.getenv("ODDS_API_KEY")
102 |     if not api_key:
103 |         raise ValueError("ODDS_API_KEY not found in environment variables")
104 |     
105 |     # Create client
106 |     client = OddsClient(api_key)
107 |     
108 |     # Get test number
109 |     test_number = get_next_test_number()
110 |     
111 |     # Get available sports
112 |     try:
113 |         sports_response = client.get_sports()
114 |         sports_file = save_response("1_available_sports.json", sports_response, test_number)
115 |         print(f"Available sports saved to {sports_file}")
116 |         print(f"Remaining requests: {sports_response['headers']['x-requests-remaining']}")
117 |     except Exception as e:
118 |         print(f"Error fetching sports: {e}")
119 |         return "", ""
120 |     
121 |     # Get NBA odds
122 |     try:
123 |         odds_options = {
124 |             "regions": "us",
125 |             "markets": "h2h,spreads",
126 |             "oddsFormat": "american"
127 |         }
128 |         odds_response = client.get_odds("basketball_nba", odds_options)
129 |         odds_file = save_response("2_nba_odds.json", odds_response, test_number)
130 |         print(f"NBA odds saved to {odds_file}")
131 |         print(f"Remaining requests: {odds_response['headers']['x-requests-remaining']}")
132 |     except Exception as e:
133 |         print(f"Error fetching NBA odds: {e}")
134 |         return sports_file, ""
135 |     
136 |     return sports_file, odds_file
137 | 
138 | 
139 | if __name__ == "__main__":
140 |     # Run the test if this file is executed directly
141 |     test_wagyu_sports()
142 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/tests/test_odds_api.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Tests for the Wagyu Sports client.
  4 | 
  5 | This file contains all tests for the Wagyu Sports client, including:
  6 | - Unit tests for the OddsClient class
  7 | - Import verification
  8 | - Installation verification
  9 | """
 10 | import os
 11 | import sys
 12 | import pytest
 13 | from unittest.mock import patch, MagicMock
 14 | import importlib.util
 15 | 
 16 | # Add the parent directory to the path so we can import the package
 17 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 18 | 
 19 | # Import the client
 20 | from wagyu_sports import OddsClient
 21 | from dotenv import load_dotenv
 22 | 
 23 | 
 24 | def test_import():
 25 |     """Test that the OddsClient can be imported."""
 26 |     # If we got this far, the import worked
 27 |     assert OddsClient is not None
 28 |     
 29 |     # Try creating an instance
 30 |     client = OddsClient("test_key")
 31 |     assert client is not None
 32 |     assert client.api_key == "test_key"
 33 | 
 34 | 
 35 | def test_dependencies():
 36 |     """Test that required dependencies are installed."""
 37 |     dependencies = ["requests", "dotenv"]
 38 |     
 39 |     for dep in dependencies:
 40 |         spec = importlib.util.find_spec(dep)
 41 |         assert spec is not None, f"{dep} is not installed"
 42 | 
 43 | 
 44 | @pytest.fixture
 45 | def client():
 46 |     """Fixture to create an OddsClient instance."""
 47 |     return OddsClient("test_api_key")
 48 | 
 49 | 
 50 | @patch('requests.get')
 51 | def test_get_sports(mock_get, client):
 52 |     """Test the get_sports method."""
 53 |     # Mock response
 54 |     mock_response = MagicMock()
 55 |     mock_response.json.return_value = [
 56 |         {"key": "sport1", "title": "Sport 1"},
 57 |         {"key": "sport2", "title": "Sport 2"}
 58 |     ]
 59 |     mock_response.headers = {
 60 |         'x-requests-remaining': '100',
 61 |         'x-requests-used': '5'
 62 |     }
 63 |     mock_get.return_value = mock_response
 64 |     
 65 |     # Call the method
 66 |     result = client.get_sports()
 67 |     
 68 |     # Verify the request
 69 |     mock_get.assert_called_once_with(
 70 |         'https://api.the-odds-api.com/v4/sports',
 71 |         params={'apiKey': 'test_api_key'}
 72 |     )
 73 |     
 74 |     # Verify the result
 75 |     assert result['data'] == mock_response.json.return_value
 76 |     assert result['headers']['x-requests-remaining'] == '100'
 77 |     assert result['headers']['x-requests-used'] == '5'
 78 | 
 79 | 
 80 | @patch('requests.get')
 81 | def test_get_odds(mock_get, client):
 82 |     """Test the get_odds method."""
 83 |     # Mock response
 84 |     mock_response = MagicMock()
 85 |     mock_response.json.return_value = [
 86 |         {
 87 |             "id": "game1",
 88 |             "home_team": "Team A",
 89 |             "away_team": "Team B",
 90 |             "bookmakers": []
 91 |         }
 92 |     ]
 93 |     mock_response.headers = {
 94 |         'x-requests-remaining': '99',
 95 |         'x-requests-used': '6'
 96 |     }
 97 |     mock_get.return_value = mock_response
 98 |     
 99 |     # Options for the request
100 |     options = {
101 |         "regions": "us",
102 |         "markets": "h2h",
103 |         "oddsFormat": "decimal"
104 |     }
105 |     
106 |     # Call the method
107 |     result = client.get_odds("basketball_nba", options)
108 |     
109 |     # Verify the request
110 |     expected_params = {'apiKey': 'test_api_key'}
111 |     expected_params.update(options)
112 |     mock_get.assert_called_once_with(
113 |         'https://api.the-odds-api.com/v4/sports/basketball_nba/odds',
114 |         params=expected_params
115 |     )
116 |     
117 |     # Verify the result
118 |     assert result['data'] == mock_response.json.return_value
119 |     assert result['headers']['x-requests-remaining'] == '99'
120 |     assert result['headers']['x-requests-used'] == '6'
121 | 
122 | 
123 | @patch('requests.get')
124 | def test_make_request_error(mock_get, client):
125 |     """Test error handling in make_request method."""
126 |     # Mock response with error
127 |     mock_response = MagicMock()
128 |     mock_response.raise_for_status.side_effect = Exception("API Error")
129 |     mock_get.return_value = mock_response
130 |     
131 |     # Call the method and expect exception
132 |     with pytest.raises(Exception):
133 |         client.make_request("/test")
134 |     
135 |     # Verify the request was made
136 |     mock_get.assert_called_once_with(
137 |         'https://api.the-odds-api.com/v4/test',
138 |         params=None
139 |     )
140 | 
141 | 
142 | def test_api_key_env():
143 |     """Test that the API key can be loaded from environment variables."""
144 |     # Load environment variables from .env file
145 |     load_dotenv(dotenv_path="config/.env")
146 |     
147 |     # Get API key
148 |     api_key = os.getenv("ODDS_API_KEY")
149 |     if not api_key:
150 |         pytest.skip("ODDS_API_KEY not set in environment")
151 |     
152 |     # If we have an API key, create a client and verify it works
153 |     # Note: This test only verifies the API key is loaded correctly,
154 |     # it does NOT make any actual API calls
155 |     client = OddsClient(api_key)
156 |     assert client.api_key == api_key
157 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/tests/test_odds_mcp_server.py:
--------------------------------------------------------------------------------

```python
  1 | """Tests for Wagyu Sports MCP server"""
  2 | 
  3 | import os
  4 | import sys
  5 | import pytest
  6 | import json
  7 | 
  8 | # Add the parent directory to the path so we can import the package
  9 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 10 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
 11 | 
 12 | from mcp.shared.memory import (
 13 |     create_connected_server_and_client_session as client_session,
 14 | )
 15 | from mcp.types import TextContent, TextResourceContents
 16 | 
 17 | from wagyu_sports.mcp_server.odds_client_server import OddsMcpServer
 18 | 
 19 | 
 20 | @pytest.mark.anyio
 21 | async def test_get_sports():
 22 |     """Test the get_sports tool"""
 23 |     server = OddsMcpServer(test_mode=True)
 24 |     
 25 |     async with client_session(server.server) as client:
 26 |         result = await client.call_tool("get_sports", {})
 27 |         assert len(result.content) == 1
 28 |         content = result.content[0]
 29 |         assert isinstance(content, TextContent)
 30 |         
 31 |         # Parse the JSON response
 32 |         response_data = json.loads(content.text)
 33 |         
 34 |         # Check that the response contains expected data
 35 |         assert "data" in response_data
 36 |         assert isinstance(response_data["data"], list)
 37 |         
 38 |         # Check for a specific sport (basketball_nba should be in the mock data)
 39 |         basketball_nba = next((s for s in response_data["data"] if s["key"] == "basketball_nba"), None)
 40 |         assert basketball_nba is not None
 41 |         assert basketball_nba["title"] == "NBA"
 42 | 
 43 | 
 44 | @pytest.mark.anyio
 45 | async def test_get_odds():
 46 |     """Test the get_odds tool"""
 47 |     server = OddsMcpServer(test_mode=True)
 48 |     
 49 |     async with client_session(server.server) as client:
 50 |         result = await client.call_tool("get_odds", {"sport": "basketball_nba"})
 51 |         assert len(result.content) == 1
 52 |         content = result.content[0]
 53 |         assert isinstance(content, TextContent)
 54 |         
 55 |         # Parse the JSON response
 56 |         response_data = json.loads(content.text)
 57 |         
 58 |         # Check that the response contains expected data
 59 |         assert "data" in response_data
 60 |         assert isinstance(response_data["data"], list)
 61 |         
 62 |         # Check that at least one game is returned
 63 |         assert len(response_data["data"]) > 0
 64 |         
 65 |         # Check that the first game has the expected fields
 66 |         first_game = response_data["data"][0]
 67 |         assert "sport_key" in first_game
 68 |         assert first_game["sport_key"] == "basketball_nba"
 69 |         assert "home_team" in first_game
 70 |         assert "away_team" in first_game
 71 |         assert "bookmakers" in first_game
 72 | 
 73 | 
 74 | @pytest.mark.anyio
 75 | async def test_get_odds_with_options():
 76 |     """Test the get_odds tool with options"""
 77 |     server = OddsMcpServer(test_mode=True)
 78 |     
 79 |     async with client_session(server.server) as client:
 80 |         result = await client.call_tool(
 81 |             "get_odds", 
 82 |             {
 83 |                 "sport": "soccer_epl",
 84 |                 "regions": "us",
 85 |                 "markets": "h2h,spreads",
 86 |                 "odds_format": "american",
 87 |                 "date_format": "iso"
 88 |             }
 89 |         )
 90 |         assert len(result.content) == 1
 91 |         content = result.content[0]
 92 |         assert isinstance(content, TextContent)
 93 |         
 94 |         # Parse the JSON response
 95 |         response_data = json.loads(content.text)
 96 |         
 97 |         # Check that the response contains expected data
 98 |         assert "data" in response_data
 99 |         assert isinstance(response_data["data"], list)
100 |         
101 |         # Check that at least one game is returned
102 |         assert len(response_data["data"]) > 0
103 |         
104 |         # Check that the first game has the expected fields
105 |         first_game = response_data["data"][0]
106 |         assert "sport_key" in first_game
107 |         assert first_game["sport_key"] == "soccer_epl"
108 | 
109 | 
110 | @pytest.mark.anyio
111 | async def test_get_quota_info():
112 |     """Test the get_quota_info tool"""
113 |     server = OddsMcpServer(test_mode=True)
114 |     
115 |     async with client_session(server.server) as client:
116 |         result = await client.call_tool("get_quota_info", {})
117 |         assert len(result.content) == 1
118 |         content = result.content[0]
119 |         assert isinstance(content, TextContent)
120 |         
121 |         # Parse the JSON response
122 |         response_data = json.loads(content.text)
123 |         
124 |         # Check that the response contains expected fields
125 |         assert "remaining_requests" in response_data
126 |         assert "used_requests" in response_data
127 | 
128 | 
129 | if __name__ == "__main__":
130 |     pytest.main(["-xvs", __file__])
131 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/examples/advanced_example.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Advanced example script demonstrating more complex usage of the Wagyu Sports client.
  4 | 
  5 | This example shows:
  6 | 1. Error handling
  7 | 2. Handling API quota limits
  8 | 3. Fetching multiple sports and odds
  9 | 4. Filtering and processing data
 10 | """
 11 | import os
 12 | import time
 13 | from datetime import datetime, timedelta
 14 | from dotenv import load_dotenv
 15 | import requests
 16 | 
 17 | from wagyu_sports import OddsClient
 18 | 
 19 | 
 20 | def format_datetime(dt_str):
 21 |     """Format ISO datetime string to a more readable format."""
 22 |     try:
 23 |         dt = datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
 24 |         return dt.strftime('%Y-%m-%d %H:%M:%S')
 25 |     except (ValueError, AttributeError):
 26 |         return dt_str
 27 | 
 28 | 
 29 | def get_upcoming_games(client, sport_key, hours=24):
 30 |     """
 31 |     Get upcoming games for a sport within the next X hours.
 32 |     
 33 |     Args:
 34 |         client (OddsClient): The Wagyu Sports client
 35 |         sport_key (str): Sport key (e.g., 'basketball_nba')
 36 |         hours (int): Number of hours to look ahead
 37 |         
 38 |     Returns:
 39 |         list: List of upcoming games
 40 |     """
 41 |     try:
 42 |         # Get odds with options
 43 |         options = {
 44 |             "regions": "us",
 45 |             "markets": "h2h",
 46 |             "oddsFormat": "decimal",
 47 |             "dateFormat": "iso"
 48 |         }
 49 |         
 50 |         response = client.get_odds(sport_key, options)
 51 |         
 52 |         # Calculate cutoff time
 53 |         now = datetime.now()
 54 |         cutoff = now + timedelta(hours=hours)
 55 |         
 56 |         # Filter games by commence time
 57 |         upcoming_games = []
 58 |         for game in response['data']:
 59 |             if 'commence_time' in game:
 60 |                 try:
 61 |                     game_time = datetime.fromisoformat(
 62 |                         game['commence_time'].replace('Z', '+00:00')
 63 |                     )
 64 |                     if now <= game_time <= cutoff:
 65 |                         upcoming_games.append(game)
 66 |                 except (ValueError, TypeError):
 67 |                     # Skip games with invalid datetime
 68 |                     continue
 69 |         
 70 |         return upcoming_games, response['headers']['x-requests-remaining']
 71 |     
 72 |     except requests.exceptions.RequestException as e:
 73 |         print(f"Error fetching odds for {sport_key}: {e}")
 74 |         return [], None
 75 | 
 76 | 
 77 | def find_best_odds(games):
 78 |     """
 79 |     Find the best odds for each team across all bookmakers.
 80 |     
 81 |     Args:
 82 |         games (list): List of games with odds
 83 |         
 84 |     Returns:
 85 |         dict: Dictionary mapping team names to their best odds
 86 |     """
 87 |     best_odds = {}
 88 |     
 89 |     for game in games:
 90 |         home_team = game.get('home_team')
 91 |         away_team = game.get('away_team')
 92 |         
 93 |         if not home_team or not away_team:
 94 |             continue
 95 |         
 96 |         # Initialize best odds for teams if not already present
 97 |         if home_team not in best_odds:
 98 |             best_odds[home_team] = 0
 99 |         if away_team not in best_odds:
100 |             best_odds[away_team] = 0
101 |         
102 |         # Check all bookmakers
103 |         for bookmaker in game.get('bookmakers', []):
104 |             for market in bookmaker.get('markets', []):
105 |                 if market.get('key') == 'h2h':
106 |                     for outcome in market.get('outcomes', []):
107 |                         team = outcome.get('name')
108 |                         price = outcome.get('price')
109 |                         
110 |                         if team and price and team in best_odds:
111 |                             best_odds[team] = max(best_odds[team], price)
112 |     
113 |     return best_odds
114 | 
115 | 
116 | def main():
117 |     """Main function demonstrating advanced usage."""
118 |     # Load environment variables
119 |     load_dotenv(dotenv_path="config/.env")
120 |     
121 |     # Get API key
122 |     api_key = os.getenv("ODDS_API_KEY")
123 |     if not api_key:
124 |         print("Error: ODDS_API_KEY not found in environment variables")
125 |         print("Please copy config/.env.example to config/.env and add your API key: ODDS_API_KEY=your_api_key_here")
126 |         return
127 |     
128 |     # Create client
129 |     client = OddsClient(api_key)
130 |     
131 |     try:
132 |         # Get available sports
133 |         print("Fetching available sports...")
134 |         sports_response = client.get_sports()
135 |         
136 |         # Check if we have sports data
137 |         if not sports_response['data']:
138 |             print("No sports data available")
139 |             return
140 |         
141 |         print(f"Found {len(sports_response['data'])} sports")
142 |         print(f"Remaining requests: {sports_response['headers']['x-requests-remaining']}")
143 |         
144 |         # Get active sports (in-season)
145 |         active_sports = []
146 |         for sport in sports_response['data']:
147 |             if sport.get('active'):
148 |                 active_sports.append(sport)
149 |         
150 |         print(f"Found {len(active_sports)} active sports")
151 |         
152 |         # Process top 3 active sports
153 |         for i, sport in enumerate(active_sports[:3]):
154 |             sport_key = sport.get('key')
155 |             sport_title = sport.get('title')
156 |             
157 |             print(f"\nProcessing {sport_title} ({sport_key})...")
158 |             
159 |             # Get upcoming games
160 |             upcoming_games, remaining = get_upcoming_games(client, sport_key, hours=48)
161 |             
162 |             if not upcoming_games:
163 |                 print(f"No upcoming games found for {sport_title}")
164 |                 continue
165 |             
166 |             print(f"Found {len(upcoming_games)} upcoming games in the next 48 hours")
167 |             
168 |             # Find best odds
169 |             best_odds = find_best_odds(upcoming_games)
170 |             
171 |             # Display results
172 |             print(f"\nUpcoming {sport_title} games:")
173 |             for game in upcoming_games[:5]:  # Show up to 5 games
174 |                 home = game.get('home_team', 'Unknown')
175 |                 away = game.get('away_team', 'Unknown')
176 |                 time_str = format_datetime(game.get('commence_time', ''))
177 |                 print(f"- {away} @ {home} (Start: {time_str})")
178 |                 
179 |                 # Show bookmakers
180 |                 if game.get('bookmakers'):
181 |                     print(f"  Available at {len(game['bookmakers'])} bookmakers")
182 |             
183 |             print(f"\nBest odds for {sport_title} teams:")
184 |             sorted_odds = sorted(best_odds.items(), key=lambda x: x[1], reverse=True)
185 |             for team, odds in sorted_odds[:5]:  # Show top 5 teams by odds
186 |                 print(f"- {team}: {odds:.2f}")
187 |             
188 |             # Check remaining requests
189 |             if remaining and int(remaining) < 10:
190 |                 print(f"\nWarning: Only {remaining} API requests remaining")
191 |                 print("Waiting 5 seconds before next request to avoid rate limiting...")
192 |                 time.sleep(5)
193 |             
194 |             print(f"Remaining requests: {remaining}")
195 |     
196 |     except Exception as e:
197 |         print(f"Error: {e}")
198 | 
199 | 
200 | if __name__ == "__main__":
201 |     main()
202 | 
```

--------------------------------------------------------------------------------
/wagyu_sports/mcp_server/odds_client_server.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Wagyu Sports MCP Server Implementation
  4 | 
  5 | This module provides an MCP server that exposes the Wagyu Sports API
  6 | functionality through the Model Context Protocol.
  7 | """
  8 | import os
  9 | import sys
 10 | import json
 11 | import asyncio
 12 | from typing import Dict, Any, Optional, List, Union
 13 | from pathlib import Path
 14 | 
 15 | from mcp.server.fastmcp import FastMCP
 16 | from mcp.server.stdio import stdio_server
 17 | import mcp.types as types
 18 | 
 19 | try:
 20 |     # When imported as a package
 21 |     from .odds_client import OddsClient
 22 | except ImportError:
 23 |     # When run directly
 24 |     from odds_client import OddsClient
 25 | 
 26 | class OddsMcpServer:
 27 |     """MCP server for Wagyu Sports odds API."""
 28 |     
 29 |     def __init__(self, api_key: Optional[str] = None, test_mode: bool = False):
 30 |         """
 31 |         Initialize the MCP server.
 32 |         
 33 |         Args:
 34 |             api_key (str, optional): API key for the Odds API. If not provided,
 35 |                                     will try to get from environment variable.
 36 |             test_mode (bool): Whether to use mock data instead of real API calls.
 37 |         """
 38 |         # Get API key from environment if not provided
 39 |         self.api_key = api_key or os.environ.get("ODDS_API_KEY")
 40 |         if not self.api_key and not test_mode:
 41 |             raise ValueError("API key is required when not in test mode")
 42 |             
 43 |         self.test_mode = test_mode
 44 |         self.mock_data_dir = Path(__file__).parent / "mocks_live"
 45 |         
 46 |         # Initialize client
 47 |         self.client = OddsClient(self.api_key) if not test_mode else None
 48 |         
 49 |         # Initialize server with FastMCP
 50 |         self.server = FastMCP("wagyu-sports-mcp")
 51 |         
 52 |         # Register tools
 53 |         self.register_tools()
 54 |     
 55 |     def register_tools(self):
 56 |         """Register MCP tools."""
 57 |         
 58 |         @self.server.tool()
 59 |         async def get_sports(all_sports: bool = False, use_test_mode: Optional[bool] = None) -> str:
 60 |             """
 61 |             Get a list of available sports.
 62 |             
 63 |             Args:
 64 |                 all_sports: Include out-of-season sports if True
 65 |                 use_test_mode: Override server test_mode setting (True for mock data, False for real API)
 66 |                 
 67 |             Returns:
 68 |                 JSON string with sports data
 69 |             """
 70 |             # Determine if we should use test mode
 71 |             test_mode = use_test_mode if use_test_mode is not None else self.test_mode
 72 |             
 73 |             if test_mode:
 74 |                 return await self._get_mock_data("sports_list_live.json")
 75 |             
 76 |             result = self.client.get_sports(all_sports=all_sports)
 77 |             return json.dumps(result, indent=2)
 78 |         
 79 |         @self.server.tool()
 80 |         async def get_odds(sport: str, regions: Optional[str] = None, 
 81 |                           markets: Optional[str] = None, 
 82 |                           odds_format: Optional[str] = None,
 83 |                           date_format: Optional[str] = None,
 84 |                           use_test_mode: Optional[bool] = None) -> str:
 85 |             """
 86 |             Get odds for a specific sport.
 87 |             
 88 |             Args:
 89 |                 sport: Sport key (e.g., 'basketball_nba')
 90 |                 regions: Comma-separated list of regions (e.g., 'us,uk')
 91 |                 markets: Comma-separated list of markets (e.g., 'h2h,spreads')
 92 |                 odds_format: Format for odds ('decimal' or 'american')
 93 |                 date_format: Format for dates ('unix' or 'iso')
 94 |                 use_test_mode: Override server test_mode setting (True for mock data, False for real API)
 95 |                 
 96 |             Returns:
 97 |                 JSON string with odds data
 98 |             """
 99 |             # Determine if we should use test mode
100 |             test_mode = use_test_mode if use_test_mode is not None else self.test_mode
101 |             
102 |             if test_mode:
103 |                 if sport == "basketball_nba":
104 |                     return await self._get_mock_data("nba_games_live.json")
105 |                 # Fall back to nba_games_live.json since we don't have a live version of game_odds_all_books.json
106 |                 return await self._get_mock_data("nba_games_live.json")
107 |             
108 |             options = {}
109 |             if regions:
110 |                 options["regions"] = regions
111 |             if markets:
112 |                 options["markets"] = markets
113 |             if odds_format:
114 |                 options["oddsFormat"] = odds_format
115 |             if date_format:
116 |                 options["dateFormat"] = date_format
117 |                 
118 |             result = self.client.get_odds(sport, options=options)
119 |             return json.dumps(result, indent=2)
120 |         
121 |         @self.server.tool()
122 |         async def get_quota_info(use_test_mode: Optional[bool] = None) -> str:
123 |             """
124 |             Get API quota information.
125 |             
126 |             Args:
127 |                 use_test_mode: Override server test_mode setting (True for mock data, False for real API)
128 |                 
129 |             Returns:
130 |                 JSON string with quota information
131 |             """
132 |             # Determine if we should use test mode
133 |             test_mode = use_test_mode if use_test_mode is not None else self.test_mode
134 |             
135 |             if test_mode:
136 |                 return await self._get_mock_data("quota_info_live.json")
137 |             
138 |             return json.dumps({
139 |                 "remaining_requests": self.client.remaining_requests,
140 |                 "used_requests": self.client.used_requests
141 |             }, indent=2)
142 |     
143 |     async def _get_mock_data(self, filename: str) -> str:
144 |         """
145 |         Get mock data from a JSON file.
146 |         
147 |         Args:
148 |             filename: Name of the mock data file
149 |             
150 |         Returns:
151 |             JSON string with mock data
152 |         """
153 |         try:
154 |             mock_file = self.mock_data_dir / filename
155 |             if not mock_file.exists():
156 |                 return json.dumps({"error": f"Mock file {filename} not found"})
157 |                 
158 |             with open(mock_file, "r") as f:
159 |                 data = json.load(f)
160 |                 
161 |             return json.dumps(data, indent=2)
162 |         except Exception as e:
163 |             return json.dumps({"error": f"Error loading mock data: {str(e)}"})
164 |     
165 |     async def run(self):
166 |         """Run the MCP server."""
167 |         # FastMCP has a different API for running the server
168 |         # We need to use the run_stdio_async method directly
169 |         await self.server.run_stdio_async()
170 |             
171 | def main():
172 |     """Run the MCP server as a standalone process."""
173 |     # Parse arguments
174 |     import argparse
175 |     parser = argparse.ArgumentParser(description="Wagyu Sports MCP Server")
176 |     parser.add_argument("--api-key", help="API key for the Odds API")
177 |     parser.add_argument("--test-mode", action="store_true", help="Use mock data instead of real API calls")
178 |     args = parser.parse_args()
179 |     
180 |     # Create and run server
181 |     server = OddsMcpServer(api_key=args.api_key, test_mode=args.test_mode)
182 |     asyncio.run(server.run())
183 | 
184 | if __name__ == "__main__":
185 |     main()
186 | 
```

--------------------------------------------------------------------------------
/old/docs/2025-01-10_odds_api_v4.md:
--------------------------------------------------------------------------------

```markdown
  1 | # The Odds API v4 Documentation
  2 | 
  3 | ## Overview
  4 | The Odds API provides comprehensive access to sports betting data, including live odds, scores, and event information from multiple bookmakers worldwide. This document serves as a detailed reference for the API's capabilities and integration points.
  5 | 
  6 | ## Host
  7 | - Primary: `https://api.the-odds-api.com`
  8 | - IPv6: `https://ipv6-api.the-odds-api.com`
  9 | 
 10 | ## Authentication
 11 | The Odds API uses API keys for authentication. All requests require an API key passed as a query parameter `apiKey`.
 12 | 
 13 | ## Available Endpoints
 14 | 
 15 | ### 1. Sports List (`GET /v4/sports`)
 16 | **Cost:** Free (doesn't count against quota)
 17 | 
 18 | **Capabilities:**
 19 | - List all in-season sports
 20 | - Option to include out-of-season sports
 21 | - Provides sport keys used in other endpoints
 22 | 
 23 | **Parameters:**
 24 | - `apiKey`: Required - API authentication key
 25 | - `all`: Optional - Include out-of-season sports if true
 26 | 
 27 | ### 2. Odds Data (`GET /v4/sports/{sport}/odds`)
 28 | **Cost:** 1 credit per region per market
 29 | 
 30 | **Capabilities:**
 31 | - Fetch odds for upcoming and live games
 32 | - Multiple betting markets support
 33 | - Regional bookmaker coverage
 34 | - Customizable odds format
 35 | - Optional bet limits information
 36 | - Bookmaker deep linking
 37 | 
 38 | **Markets Available:**
 39 | - `h2h`: Head to head/moneyline
 40 | - `spreads`: Points handicaps
 41 | - `totals`: Over/under
 42 | - `outrights`: Futures
 43 | - `h2h_lay`: Lay odds (betting exchanges)
 44 | - `outrights_lay`: Outright lay odds
 45 | 
 46 | **Regions Available:**
 47 | - `us`: United States
 48 | - `us2`: United States (additional bookmakers)
 49 | - `uk`: United Kingdom
 50 | - `au`: Australia
 51 | - `eu`: Europe
 52 | 
 53 | **Parameters:**
 54 | - `sport`: Required - Sport key from sports list
 55 | - `regions`: Required - Comma-separated list of regions
 56 | - `markets`: Optional - Comma-separated list of markets (default: h2h)
 57 | - `dateFormat`: Optional - 'unix' or 'iso' (default: iso)
 58 | - `oddsFormat`: Optional - 'decimal' or 'american' (default: decimal)
 59 | - `eventIds`: Optional - Filter specific events
 60 | - `bookmakers`: Optional - Filter specific bookmakers
 61 | - `commenceTimeFrom`: Optional - Filter by start time (ISO 8601)
 62 | - `commenceTimeTo`: Optional - Filter by end time (ISO 8601)
 63 | - `includeLinks`: Optional - Include bookmaker links
 64 | - `includeSids`: Optional - Include source IDs
 65 | - `includeBetLimits`: Optional - Include betting limits
 66 | 
 67 | ### 3. Scores (`GET /v4/sports/{sport}/scores`)
 68 | **Cost:** 
 69 | - 1 credit for live and upcoming games
 70 | - 2 credits when including historical data
 71 | 
 72 | **Capabilities:**
 73 | - Live scores (30-second updates)
 74 | - Historical scores (up to 3 days)
 75 | - Upcoming game schedules
 76 | - Detailed scoring information
 77 | - Match status tracking
 78 | 
 79 | **Parameters:**
 80 | - `sport`: Required - Sport key
 81 | - `daysFrom`: Optional - Historical data (1-3 days)
 82 | - `dateFormat`: Optional - 'unix' or 'iso'
 83 | - `eventIds`: Optional - Filter specific events
 84 | 
 85 | ### 4. Events (`GET /v4/sports/{sport}/events`)
 86 | **Cost:** Free (doesn't count against quota)
 87 | 
 88 | **Capabilities:**
 89 | - List in-play and pre-match events
 90 | - Basic game information
 91 | - Team details
 92 | - Event scheduling
 93 | - Date range filtering
 94 | 
 95 | **Parameters:**
 96 | - `sport`: Required - Sport key
 97 | - `dateFormat`: Optional - 'unix' or 'iso'
 98 | - `eventIds`: Optional - Filter specific events
 99 | - `commenceTimeFrom`: Optional - Start time filter (ISO 8601)
100 | - `commenceTimeTo`: Optional - End time filter (ISO 8601)
101 | 
102 | ### 5. Event-Specific Odds (`GET /v4/sports/{sport}/events/{eventId}/odds`)
103 | **Cost:** Varies based on markets and regions
104 | 
105 | **Capabilities:**
106 | - Detailed odds for single events
107 | - All available betting markets
108 | - Market-specific descriptions
109 | - Granular market updates
110 | - Same parameter options as main odds endpoint
111 | 
112 | ### 6. Participants (`GET /v4/sports/{sport}/participants`)
113 | **Cost:** 1 credit
114 | 
115 | **Capabilities:**
116 | - List all participants (teams or individuals) for a sport
117 | - Does not include players on teams
118 | - Includes both active and inactive participants
119 | 
120 | **Parameters:**
121 | - `sport`: Required - Sport key
122 | - `apiKey`: Required - API authentication key
123 | 
124 | ### 7. Historical Odds (`GET /v4/historical/sports/{sport}/odds`)
125 | **Cost:** 10 credits per region per market
126 | 
127 | **Capabilities:**
128 | - Historical odds data from June 6th, 2020
129 | - 10-minute intervals until September 2022
130 | - 5-minute intervals after September 2022
131 | - Available only on paid plans
132 | 
133 | **Parameters:**
134 | - All parameters from the odds endpoint, plus:
135 | - `date`: Required - Timestamp for historical data (ISO 8601)
136 | 
137 | ### 8. Historical Events (`GET /v4/historical/sports/{sport}/events`)
138 | **Cost:** 1 credit (free if no events found)
139 | 
140 | **Capabilities:**
141 | - List historical events at a specified timestamp
142 | - Includes event details without odds
143 | - Useful for finding historical event IDs
144 | 
145 | **Parameters:**
146 | - Same as events endpoint, plus:
147 | - `date`: Required - Timestamp for historical data (ISO 8601)
148 | 
149 | ### 9. Historical Event Odds (`GET /v4/historical/sports/{sport}/events/{eventId}/odds`)
150 | **Cost:** 10 credits per region per market
151 | 
152 | **Capabilities:**
153 | - Historical odds for a single event
154 | - Support for all betting markets
155 | - Additional markets available after May 3rd, 2023
156 | - Available only on paid plans
157 | 
158 | **Parameters:**
159 | - Same as event-specific odds endpoint, plus:
160 | - `date`: Required - Timestamp for historical data (ISO 8601)
161 | 
162 | ## Integration Notes
163 | 
164 | ### Quota Management
165 | - Track usage through response headers:
166 |   - `x-requests-remaining`: Credits remaining until quota reset
167 |   - `x-requests-used`: Credits used since last quota reset
168 |   - `x-requests-last`: Usage cost of last API call
169 | - Costs vary by endpoint and parameters
170 | - Some endpoints are free
171 | - Multiple markets/regions multiply quota cost
172 | 
173 | ### Best Practices
174 | 1. Use free endpoints for basic data
175 | 2. Batch requests when possible
176 | 3. Cache responses when appropriate
177 | 4. Monitor quota usage headers
178 | 5. Use event-specific endpoint for detailed market data
179 | 6. Filter by eventIds to reduce data volume
180 | 
181 | ### Data Updates
182 | - Live scores update ~every 30 seconds
183 | - Odds updates vary by market and bookmaker
184 | - Events may become temporarily unavailable between rounds
185 | - Completed events are removed from odds endpoints
186 | - Historical scores available up to 3 days
187 | 
188 | ### Special Features
189 | 1. **Betting Exchange Support**
190 |    - Automatic lay odds inclusion
191 |    - Bet limits information
192 |    - Exchange-specific markets
193 | 
194 | 2. **Deep Linking**
195 |    - Bookmaker event links
196 |    - Market-specific links
197 |    - Betslip integration
198 |    - Source IDs for custom linking
199 | 
200 | 3. **Market Coverage**
201 |    - Full coverage for major markets
202 |    - Expanding coverage for additional markets
203 |    - Sport-specific market availability
204 |    - Regional variations in coverage
205 | 
206 | ## Rate Limiting
207 | - Requests are rate limited to protect systems
208 | - Status code 429 indicates rate limit reached
209 | - Space out requests over several seconds when rate limited
210 | - Quota reset period defined by subscription
211 | - Usage tracked per endpoint
212 | - Some endpoints exempt from quota
213 | - Multiple markets/regions affect usage
214 | - Remaining quota in response headers
215 | 
216 | ## Error Handling
217 | - API returns standard HTTP status codes
218 | - Error messages include descriptive text
219 | - Quota exceeded returns specific error
220 | - Invalid parameters clearly identified
221 | - Rate limiting information included
```

--------------------------------------------------------------------------------
/wagyu_sports/mcp_server/mocks_live/sports_list_live.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "_metadata": {
  3 |     "captured_at": "2025-03-03T01:43:40-08:00",
  4 |     "description": "Live data captured from the Odds API",
  5 |     "tool": "get_sports",
  6 |     "parameters": {
  7 |       "all_sports": true
  8 |     }
  9 |   },
 10 |   "data": [
 11 |     {
 12 |       "key": "americanfootball_cfl",
 13 |       "group": "American Football",
 14 |       "title": "CFL",
 15 |       "description": "Canadian Football League",
 16 |       "active": false,
 17 |       "has_outrights": false
 18 |     },
 19 |     {
 20 |       "key": "americanfootball_ncaaf",
 21 |       "group": "American Football",
 22 |       "title": "NCAAF",
 23 |       "description": "US College Football",
 24 |       "active": true,
 25 |       "has_outrights": false
 26 |     },
 27 |     {
 28 |       "key": "americanfootball_ncaaf_championship_winner",
 29 |       "group": "American Football",
 30 |       "title": "NCAAF Championship Winner",
 31 |       "description": "US College Football Championship Winner",
 32 |       "active": true,
 33 |       "has_outrights": true
 34 |     },
 35 |     {
 36 |       "key": "americanfootball_nfl",
 37 |       "group": "American Football",
 38 |       "title": "NFL",
 39 |       "description": "US Football",
 40 |       "active": false,
 41 |       "has_outrights": false
 42 |     },
 43 |     {
 44 |       "key": "americanfootball_nfl_preseason",
 45 |       "group": "American Football",
 46 |       "title": "NFL Preseason",
 47 |       "description": "US Football",
 48 |       "active": false,
 49 |       "has_outrights": false
 50 |     },
 51 |     {
 52 |       "key": "americanfootball_nfl_super_bowl_winner",
 53 |       "group": "American Football",
 54 |       "title": "NFL Super Bowl Winner",
 55 |       "description": "Super Bowl Winner 2025/2026",
 56 |       "active": true,
 57 |       "has_outrights": true
 58 |     },
 59 |     {
 60 |       "key": "americanfootball_ufl",
 61 |       "group": "American Football",
 62 |       "title": "UFL",
 63 |       "description": "United Football League",
 64 |       "active": false,
 65 |       "has_outrights": false
 66 |     },
 67 |     {
 68 |       "key": "aussierules_afl",
 69 |       "group": "Aussie Rules",
 70 |       "title": "AFL",
 71 |       "description": "Aussie Football",
 72 |       "active": true,
 73 |       "has_outrights": false
 74 |     },
 75 |     {
 76 |       "key": "baseball_kbo",
 77 |       "group": "Baseball",
 78 |       "title": "KBO",
 79 |       "description": "KBO League",
 80 |       "active": false,
 81 |       "has_outrights": false
 82 |     },
 83 |     {
 84 |       "key": "baseball_milb",
 85 |       "group": "Baseball",
 86 |       "title": "MiLB",
 87 |       "description": "Minor League Baseball",
 88 |       "active": false,
 89 |       "has_outrights": false
 90 |     },
 91 |     {
 92 |       "key": "baseball_mlb",
 93 |       "group": "Baseball",
 94 |       "title": "MLB",
 95 |       "description": "Major League Baseball",
 96 |       "active": true,
 97 |       "has_outrights": false
 98 |     },
 99 |     {
100 |       "key": "baseball_mlb_preseason",
101 |       "group": "Baseball",
102 |       "title": "MLB Preseason",
103 |       "description": "Major League Baseball",
104 |       "active": true,
105 |       "has_outrights": false
106 |     },
107 |     {
108 |       "key": "baseball_mlb_world_series_winner",
109 |       "group": "Baseball",
110 |       "title": "MLB World Series Winner",
111 |       "description": "World Series Winner 2025",
112 |       "active": true,
113 |       "has_outrights": true
114 |     },
115 |     {
116 |       "key": "baseball_ncaa",
117 |       "group": "Baseball",
118 |       "title": "NCAA Baseball",
119 |       "description": "US College Baseball",
120 |       "active": false,
121 |       "has_outrights": false
122 |     },
123 |     {
124 |       "key": "baseball_npb",
125 |       "group": "Baseball",
126 |       "title": "NPB",
127 |       "description": "Nippon Professional Baseball",
128 |       "active": false,
129 |       "has_outrights": false
130 |     },
131 |     {
132 |       "key": "basketball_euroleague",
133 |       "group": "Basketball",
134 |       "title": "Basketball Euroleague",
135 |       "description": "Basketball Euroleague",
136 |       "active": true,
137 |       "has_outrights": false
138 |     },
139 |     {
140 |       "key": "basketball_nba",
141 |       "group": "Basketball",
142 |       "title": "NBA",
143 |       "description": "US Basketball",
144 |       "active": true,
145 |       "has_outrights": false
146 |     },
147 |     {
148 |       "key": "basketball_nba_championship_winner",
149 |       "group": "Basketball",
150 |       "title": "NBA Championship Winner",
151 |       "description": "Championship Winner 2024/2025",
152 |       "active": true,
153 |       "has_outrights": true
154 |     },
155 |     {
156 |       "key": "basketball_nba_preseason",
157 |       "group": "Basketball",
158 |       "title": "NBA Preseason",
159 |       "description": "US Basketball",
160 |       "active": false,
161 |       "has_outrights": false
162 |     },
163 |     {
164 |       "key": "basketball_nbl",
165 |       "group": "Basketball",
166 |       "title": "NBL",
167 |       "description": "AU National Basketball League",
168 |       "active": true,
169 |       "has_outrights": false
170 |     },
171 |     {
172 |       "key": "basketball_ncaab",
173 |       "group": "Basketball",
174 |       "title": "NCAAB",
175 |       "description": "US College Basketball",
176 |       "active": true,
177 |       "has_outrights": false
178 |     },
179 |     {
180 |       "key": "basketball_ncaab_championship_winner",
181 |       "group": "Basketball",
182 |       "title": "NCAAB Championship Winner",
183 |       "description": "US College Basketball Championship Winner",
184 |       "active": true,
185 |       "has_outrights": true
186 |     },
187 |     {
188 |       "key": "basketball_wnba",
189 |       "group": "Basketball",
190 |       "title": "WNBA",
191 |       "description": "US Basketball",
192 |       "active": false,
193 |       "has_outrights": false
194 |     },
195 |     {
196 |       "key": "basketball_wncaab",
197 |       "group": "Basketball",
198 |       "title": "WNCAAB",
199 |       "description": "US Women's College Basketball",
200 |       "active": false,
201 |       "has_outrights": false
202 |     },
203 |     {
204 |       "key": "boxing_boxing",
205 |       "group": "Boxing",
206 |       "title": "Boxing",
207 |       "description": "Boxing Bouts",
208 |       "active": true,
209 |       "has_outrights": false
210 |     },
211 |     {
212 |       "key": "cricket_asia_cup",
213 |       "group": "Cricket",
214 |       "title": "Asia Cup",
215 |       "description": "Asia Cup",
216 |       "active": false,
217 |       "has_outrights": false
218 |     },
219 |     {
220 |       "key": "cricket_big_bash",
221 |       "group": "Cricket",
222 |       "title": "Big Bash",
223 |       "description": "Big Bash League",
224 |       "active": false,
225 |       "has_outrights": false
226 |     },
227 |     {
228 |       "key": "cricket_caribbean_premier_league",
229 |       "group": "Cricket",
230 |       "title": "CPLT20",
231 |       "description": "Caribbean Premier League",
232 |       "active": false,
233 |       "has_outrights": false
234 |     },
235 |     {
236 |       "key": "cricket_icc_trophy",
237 |       "group": "Cricket",
238 |       "title": "ICC Champions Trophy",
239 |       "description": "ICC Champions Trophy",
240 |       "active": true,
241 |       "has_outrights": false
242 |     },
243 |     {
244 |       "key": "cricket_icc_world_cup",
245 |       "group": "Cricket",
246 |       "title": "ICC World Cup",
247 |       "description": "ICC World Cup",
248 |       "active": false,
249 |       "has_outrights": false
250 |     },
251 |     {
252 |       "key": "cricket_international_t20",
253 |       "group": "Cricket",
254 |       "title": "International Twenty20",
255 |       "description": "International Twenty20",
256 |       "active": false,
257 |       "has_outrights": false
258 |     },
259 |     {
260 |       "key": "cricket_ipl",
261 |       "group": "Cricket",
262 |       "title": "IPL",
263 |       "description": "Indian Premier League",
264 |       "active": false,
265 |       "has_outrights": false
266 |     },
267 |     {
268 |       "key": "cricket_odi",
269 |       "group": "Cricket",
270 |       "title": "One Day Internationals",
271 |       "description": "One Day Internationals",
272 |       "active": false,
273 |       "has_outrights": false
274 |     },
275 |     {
276 |       "key": "cricket_psl",
277 |       "group": "Cricket",
278 |       "title": "Pakistan Super League",
279 |       "description": "Pakistan Super League",
280 |       "active": false,
281 |       "has_outrights": false
282 |     },
283 |     {
284 |       "key": "cricket_t20_blast",
285 |       "group": "Cricket",
286 |       "title": "T20 Blast",
287 |       "description": "T20 Blast",
288 |       "active": false,
289 |       "has_outrights": false
290 |     },
291 |     {
292 |       "key": "cricket_test_match",
293 |       "group": "Cricket",
294 |       "title": "Test Matches",
295 |       "description": "International Test Matches",
296 |       "active": false,
297 |       "has_outrights": false
298 |     },
299 |     {
300 |       "key": "cricket_the_hundred",
301 |       "group": "Cricket",
302 |       "title": "The Hundred",
303 |       "description": "The Hundred",
304 |       "active": false,
305 |       "has_outrights": false
306 |     },
307 |     {
308 |       "key": "golf_masters_tournament_winner",
309 |       "group": "Golf",
310 |       "title": "Masters Tournament Winner",
311 |       "description": "2025 Winner",
312 |       "active": true,
313 |       "has_outrights": true
314 |     },
315 |     {
316 |       "key": "golf_pga_championship_winner",
317 |       "group": "Golf",
318 |       "title": "PGA Championship Winner",
319 |       "description": "2025 Winner",
320 |       "active": true,
321 |       "has_outrights": true
322 |     },
323 |     {
324 |       "key": "golf_the_open_championship_winner",
325 |       "group": "Golf",
326 |       "title": "The Open Winner",
327 |       "description": "2025 Winner",
328 |       "active": true,
329 |       "has_outrights": true
330 |     },
331 |     {
332 |       "key": "golf_us_open_winner",
333 |       "group": "Golf",
334 |       "title": "US Open Winner",
335 |       "description": "2025 Winner",
336 |       "active": true,
337 |       "has_outrights": true
338 |     },
339 |     {
340 |       "key": "icehockey_ahl",
341 |       "group": "Ice Hockey",
342 |       "title": "AHL",
343 |       "description": "American Hockey League",
344 |       "active": false,
345 |       "has_outrights": false
346 |     },
347 |     {
348 |       "key": "icehockey_liiga",
349 |       "group": "Ice Hockey",
350 |       "title": "Liiga",
351 |       "description": "Finnish SM League",
352 |       "active": true,
353 |       "has_outrights": false
354 |     },
355 |     {
356 |       "key": "icehockey_mestis",
357 |       "group": "Ice Hockey",
358 |       "title": "Mestis",
359 |       "description": "Finnish Mestis League",
360 |       "active": true,
361 |       "has_outrights": false
362 |     },
363 |     {
364 |       "key": "icehockey_nhl",
365 |       "group": "Ice Hockey",
366 |       "title": "NHL",
367 |       "description": "US Ice Hockey",
368 |       "active": true,
369 |       "has_outrights": false
370 |     },
371 |     {
372 |       "key": "icehockey_nhl_championship_winner",
373 |       "group": "Ice Hockey",
374 |       "title": "NHL Championship Winner",
375 |       "description": "Stanley Cup Winner 2024/2025",
376 |       "active": true,
377 |       "has_outrights": true
378 |     },
379 |     {
380 |       "key": "icehockey_sweden_allsvenskan",
381 |       "group": "Ice Hockey",
382 |       "title": "HockeyAllsvenskan",
383 |       "description": "Swedish Hockey Allsvenskan",
384 |       "active": true,
385 |       "has_outrights": false
386 |     },
387 |     {
388 |       "key": "icehockey_sweden_hockey_league",
389 |       "group": "Ice Hockey",
390 |       "title": "SHL",
391 |       "description": "Swedish Hockey League",
392 |       "active": true,
393 |       "has_outrights": false
394 |     },
395 |     {
396 |       "key": "lacrosse_ncaa",
397 |       "group": "Lacrosse",
398 |       "title": "NCAA Lacrosse",
399 |       "description": "College Lacrosse",
400 |       "active": true,
401 |       "has_outrights": false
402 |     },
403 |     {
404 |       "key": "lacrosse_pll",
405 |       "group": "Lacrosse",
406 |       "title": "PLL",
407 |       "description": "Premier Lacrosse League",
408 |       "active": false,
409 |       "has_outrights": false
410 |     },
411 |     {
412 |       "key": "mma_mixed_martial_arts",
413 |       "group": "Mixed Martial Arts",
414 |       "title": "MMA",
415 |       "description": "Mixed Martial Arts",
416 |       "active": true,
417 |       "has_outrights": false
418 |     },
419 |     {
420 |       "key": "politics_us_presidential_election_winner",
421 |       "group": "Politics",
422 |       "title": "US Presidential Elections Winner",
423 |       "description": "2028 US Presidential Election Winner",
424 |       "active": false,
425 |       "has_outrights": true
426 |     },
427 |     {
428 |       "key": "rugbyleague_nrl",
429 |       "group": "Rugby League",
430 |       "title": "NRL",
431 |       "description": "Aussie Rugby League",
432 |       "active": true,
433 |       "has_outrights": false
434 |     },
435 |     {
436 |       "key": "rugbyunion_six_nations",
437 |       "group": "Rugby Union",
438 |       "title": "Six Nations",
439 |       "description": "Six Nations Championship",
440 |       "active": true,
441 |       "has_outrights": false
442 |     },
443 |     {
444 |       "key": "soccer_africa_cup_of_nations",
445 |       "group": "Soccer",
446 |       "title": "Africa Cup of Nations",
447 |       "description": "Africa Cup of Nations",
448 |       "active": false,
449 |       "has_outrights": false
450 |     },
451 |     {
452 |       "key": "soccer_argentina_primera_division",
453 |       "group": "Soccer",
454 |       "title": "Primera Divisi\u00f3n - Argentina",
455 |       "description": "Argentine Primera Divisi\u00f3n",
456 |       "active": true,
457 |       "has_outrights": false
458 |     },
459 |     {
460 |       "key": "soccer_australia_aleague",
461 |       "group": "Soccer",
462 |       "title": "A-League",
463 |       "description": "Aussie Soccer",
464 |       "active": true,
465 |       "has_outrights": false
466 |     },
467 |     {
468 |       "key": "soccer_austria_bundesliga",
469 |       "group": "Soccer",
470 |       "title": "Austrian Football Bundesliga",
471 |       "description": "Austrian Soccer",
472 |       "active": true,
473 |       "has_outrights": false
474 |     },
475 |     {
476 |       "key": "soccer_belgium_first_div",
477 |       "group": "Soccer",
478 |       "title": "Belgium First Div",
479 |       "description": "Belgian First Division A",
480 |       "active": true,
481 |       "has_outrights": false
482 |     },
483 |     {
484 |       "key": "soccer_brazil_campeonato",
485 |       "group": "Soccer",
486 |       "title": "Brazil S\u00e9rie A",
487 |       "description": "Brasileir\u00e3o S\u00e9rie A",
488 |       "active": true,
489 |       "has_outrights": false
490 |     },
491 |     {
492 |       "key": "soccer_brazil_serie_b",
493 |       "group": "Soccer",
494 |       "title": "Brazil S\u00e9rie B",
495 |       "description": "Campeonato Brasileiro S\u00e9rie B",
496 |       "active": false,
497 |       "has_outrights": false
498 |     },
499 |     {
500 |       "key": "soccer_chile_campeonato",
501 |       "group": "Soccer",
502 |       "title": "Primera Divisi\u00f3n - Chile",
503 |       "description": "Campeonato Chileno",
504 |       "active": true,
505 |       "has_outrights": false
506 |     },
507 |     {
508 |       "key": "soccer_china_superleague",
509 |       "group": "Soccer",
510 |       "title": "Super League - China",
511 |       "description": "Chinese Soccer",
512 |       "active": false,
513 |       "has_outrights": false
514 |     },
515 |     {
516 |       "key": "soccer_conmebol_copa_america",
517 |       "group": "Soccer",
518 |       "title": "Copa Am\u00e9rica",
519 |       "description": "CONMEBOL Copa Am\u00e9rica",
520 |       "active": false,
521 |       "has_outrights": false
522 |     },
523 |     {
524 |       "key": "soccer_conmebol_copa_libertadores",
525 |       "group": "Soccer",
526 |       "title": "Copa Libertadores",
527 |       "description": "CONMEBOL Copa Libertadores",
528 |       "active": true,
529 |       "has_outrights": false
530 |     },
531 |     {
532 |       "key": "soccer_denmark_superliga",
533 |       "group": "Soccer",
534 |       "title": "Denmark Superliga",
535 |       "description": "Danish Soccer",
536 |       "active": true,
537 |       "has_outrights": false
538 |     },
539 |     {
540 |       "key": "soccer_efl_champ",
541 |       "group": "Soccer",
542 |       "title": "Championship",
543 |       "description": "EFL Championship",
544 |       "active": true,
545 |       "has_outrights": false
546 |     },
547 |     {
548 |       "key": "soccer_england_efl_cup",
549 |       "group": "Soccer",
550 |       "title": "EFL Cup",
551 |       "description": "League Cup",
552 |       "active": true,
553 |       "has_outrights": false
554 |     },
555 |     {
556 |       "key": "soccer_england_league1",
557 |       "group": "Soccer",
558 |       "title": "League 1",
559 |       "description": "EFL League 1",
560 |       "active": true,
561 |       "has_outrights": false
562 |     },
563 |     {
564 |       "key": "soccer_england_league2",
565 |       "group": "Soccer",
566 |       "title": "League 2",
567 |       "description": "EFL League 2 ",
568 |       "active": true,
569 |       "has_outrights": false
570 |     },
571 |     {
572 |       "key": "soccer_epl",
573 |       "group": "Soccer",
574 |       "title": "EPL",
575 |       "description": "English Premier League",
576 |       "active": true,
577 |       "has_outrights": false
578 |     },
579 |     {
580 |       "key": "soccer_fa_cup",
581 |       "group": "Soccer",
582 |       "title": "FA Cup",
583 |       "description": "Football Association Challenge Cup",
584 |       "active": true,
585 |       "has_outrights": false
586 |     },
587 |     {
588 |       "key": "soccer_fifa_world_cup",
589 |       "group": "Soccer",
590 |       "title": "FIFA World Cup",
591 |       "description": "FIFA World Cup 2022",
592 |       "active": false,
593 |       "has_outrights": false
594 |     },
595 |     {
596 |       "key": "soccer_fifa_world_cup_winner",
597 |       "group": "Soccer",
598 |       "title": "FIFA World Cup Winner",
599 |       "description": "FIFA World Cup Winner 2026",
600 |       "active": true,
601 |       "has_outrights": true
602 |     },
603 |     {
604 |       "key": "soccer_fifa_world_cup_womens",
605 |       "group": "Soccer",
606 |       "title": "FIFA Women's World Cup",
607 |       "description": "FIFA Women's World Cup",
608 |       "active": false,
609 |       "has_outrights": false
610 |     },
611 |     {
612 |       "key": "soccer_finland_veikkausliiga",
613 |       "group": "Soccer",
614 |       "title": "Veikkausliiga - Finland",
615 |       "description": "Finnish  Soccer",
616 |       "active": false,
617 |       "has_outrights": false
618 |     },
619 |     {
620 |       "key": "soccer_france_ligue_one",
621 |       "group": "Soccer",
622 |       "title": "Ligue 1 - France",
623 |       "description": "French Soccer",
624 |       "active": true,
625 |       "has_outrights": false
626 |     },
627 |     {
628 |       "key": "soccer_france_ligue_two",
629 |       "group": "Soccer",
630 |       "title": "Ligue 2 - France",
631 |       "description": "French Soccer",
632 |       "active": true,
633 |       "has_outrights": false
634 |     },
635 |     {
636 |       "key": "soccer_germany_bundesliga",
637 |       "group": "Soccer",
638 |       "title": "Bundesliga - Germany",
639 |       "description": "German Soccer",
640 |       "active": true,
641 |       "has_outrights": false
642 |     },
643 |     {
644 |       "key": "soccer_germany_bundesliga2",
645 |       "group": "Soccer",
646 |       "title": "Bundesliga 2 - Germany",
647 |       "description": "German Soccer",
648 |       "active": true,
649 |       "has_outrights": false
650 |     },
651 |     {
652 |       "key": "soccer_germany_liga3",
653 |       "group": "Soccer",
654 |       "title": "3. Liga - Germany",
655 |       "description": "German Soccer",
656 |       "active": true,
657 |       "has_outrights": false
658 |     },
659 |     {
660 |       "key": "soccer_greece_super_league",
661 |       "group": "Soccer",
662 |       "title": "Super League - Greece",
663 |       "description": "Greek Soccer",
664 |       "active": true,
665 |       "has_outrights": false
666 |     },
667 |     {
668 |       "key": "soccer_italy_serie_a",
669 |       "group": "Soccer",
670 |       "title": "Serie A - Italy",
671 |       "description": "Italian Soccer",
672 |       "active": true,
673 |       "has_outrights": false
674 |     },
675 |     {
676 |       "key": "soccer_italy_serie_b",
677 |       "group": "Soccer",
678 |       "title": "Serie B - Italy",
679 |       "description": "Italian Soccer",
680 |       "active": true,
681 |       "has_outrights": false
682 |     },
683 |     {
684 |       "key": "soccer_japan_j_league",
685 |       "group": "Soccer",
686 |       "title": "J League",
687 |       "description": "Japan Soccer League",
688 |       "active": true,
689 |       "has_outrights": false
690 |     },
691 |     {
692 |       "key": "soccer_korea_kleague1",
693 |       "group": "Soccer",
694 |       "title": "K League 1",
695 |       "description": "Korean Soccer",
696 |       "active": true,
697 |       "has_outrights": false
698 |     },
699 |     {
700 |       "key": "soccer_league_of_ireland",
701 |       "group": "Soccer",
702 |       "title": "League of Ireland",
703 |       "description": "Airtricity League Premier Division",
704 |       "active": true,
705 |       "has_outrights": false
706 |     },
707 |     {
708 |       "key": "soccer_mexico_ligamx",
709 |       "group": "Soccer",
710 |       "title": "Liga MX",
711 |       "description": "Mexican Soccer",
712 |       "active": true,
713 |       "has_outrights": false
714 |     },
715 |     {
716 |       "key": "soccer_netherlands_eredivisie",
717 |       "group": "Soccer",
718 |       "title": "Dutch Eredivisie",
719 |       "description": "Dutch Soccer",
720 |       "active": true,
721 |       "has_outrights": false
722 |     },
723 |     {
724 |       "key": "soccer_norway_eliteserien",
725 |       "group": "Soccer",
726 |       "title": "Eliteserien - Norway",
727 |       "description": "Norwegian Soccer",
728 |       "active": true,
729 |       "has_outrights": false
730 |     },
731 |     {
732 |       "key": "soccer_poland_ekstraklasa",
733 |       "group": "Soccer",
734 |       "title": "Ekstraklasa - Poland",
735 |       "description": "Polish Soccer",
736 |       "active": true,
737 |       "has_outrights": false
738 |     },
739 |     {
740 |       "key": "soccer_portugal_primeira_liga",
741 |       "group": "Soccer",
742 |       "title": "Primeira Liga - Portugal",
743 |       "description": "Portugese Soccer",
744 |       "active": true,
745 |       "has_outrights": false
746 |     },
747 |     {
748 |       "key": "soccer_spain_la_liga",
749 |       "group": "Soccer",
750 |       "title": "La Liga - Spain",
751 |       "description": "Spanish Soccer",
752 |       "active": true,
753 |       "has_outrights": false
754 |     },
755 |     {
756 |       "key": "soccer_spain_segunda_division",
757 |       "group": "Soccer",
758 |       "title": "La Liga 2 - Spain",
759 |       "description": "Spanish Soccer",
760 |       "active": true,
761 |       "has_outrights": false
762 |     },
763 |     {
764 |       "key": "soccer_spl",
765 |       "group": "Soccer",
766 |       "title": "Premiership - Scotland",
767 |       "description": "Scottish Premiership",
768 |       "active": true,
769 |       "has_outrights": false
770 |     },
771 |     {
772 |       "key": "soccer_sweden_allsvenskan",
773 |       "group": "Soccer",
774 |       "title": "Allsvenskan - Sweden",
775 |       "description": "Swedish Soccer",
776 |       "active": true,
777 |       "has_outrights": false
778 |     },
779 |     {
780 |       "key": "soccer_sweden_superettan",
781 |       "group": "Soccer",
782 |       "title": "Superettan - Sweden",
783 |       "description": "Swedish Soccer",
784 |       "active": false,
785 |       "has_outrights": false
786 |     },
787 |     {
788 |       "key": "soccer_switzerland_superleague",
789 |       "group": "Soccer",
790 |       "title": "Swiss Superleague",
791 |       "description": "Swiss Soccer",
792 |       "active": true,
793 |       "has_outrights": false
794 |     },
795 |     {
796 |       "key": "soccer_turkey_super_league",
797 |       "group": "Soccer",
798 |       "title": "Turkey Super League",
799 |       "description": "Turkish Soccer",
800 |       "active": true,
801 |       "has_outrights": false
802 |     },
803 |     {
804 |       "key": "soccer_uefa_champs_league",
805 |       "group": "Soccer",
806 |       "title": "UEFA Champions League",
807 |       "description": "European Champions League",
808 |       "active": true,
809 |       "has_outrights": false
810 |     },
811 |     {
812 |       "key": "soccer_uefa_champs_league_qualification",
813 |       "group": "Soccer",
814 |       "title": "UEFA Champions League Qualification",
815 |       "description": "European Champions League Qualification",
816 |       "active": false,
817 |       "has_outrights": false
818 |     },
819 |     {
820 |       "key": "soccer_uefa_euro_qualification",
821 |       "group": "Soccer",
822 |       "title": "UEFA Euro Qualification",
823 |       "description": "European Championship Qualification",
824 |       "active": false,
825 |       "has_outrights": false
826 |     },
827 |     {
828 |       "key": "soccer_uefa_europa_conference_league",
829 |       "group": "Soccer",
830 |       "title": "UEFA Europa Conference League",
831 |       "description": "UEFA Europa Conference League",
832 |       "active": true,
833 |       "has_outrights": false
834 |     },
835 |     {
836 |       "key": "soccer_uefa_europa_league",
837 |       "group": "Soccer",
838 |       "title": "UEFA Europa League",
839 |       "description": "European Europa League",
840 |       "active": true,
841 |       "has_outrights": false
842 |     },
843 |     {
844 |       "key": "soccer_uefa_european_championship",
845 |       "group": "Soccer",
846 |       "title": "UEFA Euro 2024",
847 |       "description": "UEFA European Championship",
848 |       "active": false,
849 |       "has_outrights": false
850 |     },
851 |     {
852 |       "key": "soccer_uefa_nations_league",
853 |       "group": "Soccer",
854 |       "title": "UEFA Nations League",
855 |       "description": "UEFA Nations League",
856 |       "active": true,
857 |       "has_outrights": false
858 |     },
859 |     {
860 |       "key": "soccer_usa_mls",
861 |       "group": "Soccer",
862 |       "title": "MLS",
863 |       "description": "Major League Soccer",
864 |       "active": true,
865 |       "has_outrights": false
866 |     },
867 |     {
868 |       "key": "tennis_atp_aus_open_singles",
869 |       "group": "Tennis",
870 |       "title": "ATP Australian Open",
871 |       "description": "Men's Singles",
872 |       "active": false,
873 |       "has_outrights": false
874 |     },
875 |     {
876 |       "key": "tennis_atp_canadian_open",
877 |       "group": "Tennis",
878 |       "title": "ATP Canadian Open",
879 |       "description": "Men's Singles",
880 |       "active": false,
881 |       "has_outrights": false
882 |     },
883 |     {
884 |       "key": "tennis_atp_china_open",
885 |       "group": "Tennis",
886 |       "title": "ATP China Open",
887 |       "description": "Men's Singles",
888 |       "active": false,
889 |       "has_outrights": false
890 |     },
891 |     {
892 |       "key": "tennis_atp_cincinnati_open",
893 |       "group": "Tennis",
894 |       "title": "ATP Cincinnati Open",
895 |       "description": "Men's Singles",
896 |       "active": false,
897 |       "has_outrights": false
898 |     },
899 |     {
900 |       "key": "tennis_atp_dubai",
901 |       "group": "Tennis",
902 |       "title": "ATP Dubai",
903 |       "description": "Men's Singles",
904 |       "active": false,
905 |       "has_outrights": false
906 |     },
907 |     {
908 |       "key": "tennis_atp_french_open",
909 |       "group": "Tennis",
910 |       "title": "ATP French Open",
911 |       "description": "Men's Singles",
912 |       "active": false,
913 |       "has_outrights": false
914 |     },
915 |     {
916 |       "key": "tennis_atp_paris_masters",
917 |       "group": "Tennis",
918 |       "title": "ATP Paris Masters",
919 |       "description": "Men's Singles",
920 |       "active": false,
921 |       "has_outrights": false
922 |     },
923 |     {
924 |       "key": "tennis_atp_qatar_open",
925 |       "group": "Tennis",
926 |       "title": "ATP Qatar Open",
927 |       "description": "Men's Singles",
928 |       "active": false,
929 |       "has_outrights": false
930 |     },
931 |     {
932 |       "key": "tennis_atp_shanghai_masters",
933 |       "group": "Tennis",
934 |       "title": "ATP Shanghai Masters",
935 |       "description": "Men's Singles",
936 |       "active": false,
937 |       "has_outrights": false
938 |     },
939 |     {
940 |       "key": "tennis_atp_us_open",
941 |       "group": "Tennis",
942 |       "title": "ATP US Open",
943 |       "description": "Men's Singles",
944 |       "active": false,
945 |       "has_outrights": false
946 |     },
947 |     {
948 |       "key": "tennis_atp_wimbledon",
949 |       "group": "Tennis",
950 |       "title": "ATP Wimbledon",
951 |       "description": "Men's Singles",
952 |       "active": false,
953 |       "has_outrights": false
954 |     },
955 |     {
956 |       "key": "tennis_wta_aus_open_singles",
957 |       "group": "Tennis",
958 |       "title": "WTA Australian Open",
959 |       "description": "Women's Singles",
960 |       "active": false,
961 |       "has_outrights": false
962 |     }
963 |   ],
964 |   "headers": {
965 |     "x-requests-remaining": "492",
966 |     "x-requests-used": "8"
967 |   }
968 | }
969 | 
```
Page 1/3FirstPrevNextLast