An MCP (Model Context Protocol) server that gives AI agents direct access to the Catapult Openfield Connect API v6. Query athlete GPS sensor data, session analytics, team rosters, performance stats, and more - all through natural language.
81 tools covering activities, athletes, 10Hz sensor data, aggregated stats, teams, periods, parameters, annotations, venues, live tracking, threshold sets, and webhooks.
Recommended clients: Claude Code or OpenClaw. This server exposes 81 tools and can make multiple API calls per query. Claude Desktop has a per-conversation tool usage cap that is easy to hit during analytics-heavy sessions. Claude Code and OpenClaw handle high tool volumes without issue.
- Python 3.11 or higher
- A Catapult Openfield account with API access
- An Openfield API token (generated in the OpenField UI)
git clone https://github.com/MatthewLeeWilcox/OpenfieldMCP.git
cd OpenfieldMCPpip install -e .This installs the openfield-mcp package and its dependencies (mcp, httpx, pydantic-settings, uvicorn).
- Log into your Openfield account
- Go to Settings → API Tokens
- Create a new token and copy it
Create a .env file in the project root (or export as shell environment variables):
CATAPULT_API_TOKEN=your_token_here
CATAPULT_REGION=us # us | eu | au| Variable | Required | Description |
|---|---|---|
CATAPULT_API_TOKEN |
Yes | Long-lived token from Openfield Settings |
CATAPULT_REGION |
Yes | us (Americas), eu (EMEA), au (Asia-Pacific) |
CATAPULT_TIMEOUT |
No | HTTP timeout in seconds (default: 30) |
CATAPULT_SENSOR_PAGE_SIZE |
No | Rows per page for 10Hz sensor data (default: 5000) |
CATAPULT_API_TOKEN=your_token CATAPULT_REGION=us python -c "
import asyncio
from openfield_mcp.settings import OpenfieldSettings
from openfield_mcp.client import OpenfieldClient
async def test():
s = OpenfieldSettings()
c = OpenfieldClient(s)
result = await c.request('GET', '/athletes', params={'page': 1, 'page_size': 1})
print('Connected -', result)
await c.aclose()
asyncio.run(test())
"Claude Code automatically picks up .mcp.json from the project directory.
Step 1: Export your credentials in your shell (add to ~/.bashrc or ~/.zshrc):
export CATAPULT_API_TOKEN=your_token_here
export CATAPULT_REGION=usStep 2: The .mcp.json file is already included in this repo:
{
"mcpServers": {
"openfield": {
"type": "stdio",
"command": "python",
"args": ["-m", "openfield_mcp.server"],
"env": {
"CATAPULT_API_TOKEN": "${CATAPULT_API_TOKEN}",
"CATAPULT_REGION": "${CATAPULT_REGION}"
}
}
}
}Step 3: Open a Claude Code session from inside this project folder:
cd /path/to/OpenfieldMCP
claudeThe openfield MCP server will register automatically. Verify with /mcp - you should see openfield listed as connected.
Step 4: Ask questions naturally:
Who had the highest player load in yesterday's session?
Show me all activities from last week.
What was the mean HSD by position group today?
OpenClaw manages MCP servers via its CLI. Run these commands once - the config persists across sessions.
Step 1: Export your credentials in your shell (add to ~/.bashrc or ~/.zshrc):
export CATAPULT_API_TOKEN=your_token_here
export CATAPULT_REGION=usStep 2: Register the server with OpenClaw:
openclaw mcp set openfield '{
"command": "python",
"args": ["-m", "openfield_mcp.server"],
"workingDirectory": "/absolute/path/to/OpenfieldMCP",
"env": {
"CATAPULT_API_TOKEN": "your_token_here",
"CATAPULT_REGION": "us"
}
}'Step 3: Verify it registered:
openclaw mcp list
openclaw mcp show openfieldThat's it - OpenClaw will launch the server automatically on demand. To remove it later:
openclaw mcp unset openfieldMac: Edit ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: Edit %APPDATA%\Claude\claude_desktop_config.json
Add the following inside "mcpServers":
{
"mcpServers": {
"openfield": {
"type": "stdio",
"command": "python",
"args": ["-m", "openfield_mcp.server"],
"cwd": "/absolute/path/to/OpenfieldMCP",
"env": {
"CATAPULT_API_TOKEN": "your_token_here",
"CATAPULT_REGION": "us"
}
}
}
}Replace
/absolute/path/to/OpenfieldMCPwith the actual path where you cloned the repo. On Windows use double backslashes:C:\\Users\\you\\OpenfieldMCP
Restart Claude Desktop. The openfield server will appear in the tools panel (hammer icon).
Project-level (only active in this project): create .cursor/mcp.json in your project root:
{
"mcpServers": {
"openfield": {
"type": "stdio",
"command": "python",
"args": ["-m", "openfield_mcp.server"],
"cwd": "/absolute/path/to/OpenfieldMCP",
"env": {
"CATAPULT_API_TOKEN": "your_token_here",
"CATAPULT_REGION": "us"
}
}
}
}Global (active in all Cursor projects): edit ~/.cursor/mcp.json with the same structure.
Restart Cursor. Navigate to Settings → MCP to confirm the server is listed as active.
Edit ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"openfield": {
"type": "stdio",
"command": "python",
"args": ["-m", "openfield_mcp.server"],
"cwd": "/absolute/path/to/OpenfieldMCP",
"env": {
"CATAPULT_API_TOKEN": "your_token_here",
"CATAPULT_REGION": "us"
}
}
}
}Reload Windsurf. The server will appear under Cascade → MCP Servers.
All integrations above launch the server automatically - you do not need to run it manually.
openfield-mcp
# or
python -m openfield_mcp.server| Category | Tools |
|---|---|
| Activities | list_activities, get_activity, get_activity_athletes, get_activity_periods, get_activity_annotations, get_activity_tags, get_activity_devices, update_activity, delete_activities, create_injected_activity, update_injected_activity, get_injection_template, inject_stats, delete_injected_stats |
| Athletes | list_athletes, get_athlete, create_athlete, update_athlete, get_athlete_annotations |
| Sensor Data | get_sensor_data, get_period_sensor_data, get_athlete_events, get_period_events, get_athlete_efforts, get_period_efforts |
| Stats | query_stats |
| Teams | list_teams, get_team, create_team, get_team_athletes, move_athletes |
| Periods | list_periods, get_period, get_period_athletes, get_positions |
| Parameters | list_parameters, list_parameter_types, get_parameter, create_parameter, update_parameter, delete_parameter |
| Tags | list_tags, create_tag, update_tag, list_tag_types, get_tag_type, create_tag_type, update_tag_type, add_entity_tags, remove_entity_tags |
| Annotations | get_annotation, create_annotation, update_annotation, delete_annotation, list_annotation_categories, get_annotation_category, create_annotation_category, update_annotation_category, delete_annotation_category |
| Venues | list_venues, get_venue, create_venue |
| Live | get_live_athletes, get_live_info |
| Threshold Sets | list_threshold_sets, get_threshold_set, create_threshold_set, update_threshold_set, delete_threshold_sets, restore_threshold_sets, get_threshold_alerts, update_threshold_alerts |
| Webhooks | list_webhook_configs, get_webhook_config, create_webhook_config, update_webhook_config, delete_webhook_config, list_webhook_events, list_webhook_events_for_config |
| Settings | get_settings, set_default_athlete_profile |
Once connected, ask in natural language:
Who had the highest player load in the last session?
What was the mean and median HSD across all sessions today?
Which athletes had outlier high-speed distance relative to their position group?
List all activities from the past week for the varsity team.
Show me 10Hz GPS data for athlete [name] in yesterday's morning session.
Which striker covered the most distance in the last game?
Create an annotation on activity [id] at the 32-minute mark.
OpenfieldMCP/
├── pyproject.toml
├── .env.example
├── .mcp.json # Claude Code / Cursor project config
└── src/
└── openfield_mcp/
├── server.py # FastMCP entry point
├── settings.py # Environment config (CATAPULT_*)
├── client.py # Authenticated httpx wrapper
└── tools/ # 81 MCP tools across 14 modules
Server not appearing in Claude Code:
- Confirm
CATAPULT_API_TOKENis exported in your shell (echo $CATAPULT_API_TOKEN) - Run
/mcpin Claude Code to see server status and error messages
422 Unprocessable Entity errors:
- Check the
filtersformat forquery_stats- each filter needsname,comparison(=,>,<,>=,<=,<>), andvalues(array)
Sensor data timeouts:
- Reduce
CATAPULT_SENSOR_PAGE_SIZE(default 5000) and paginate using thepageparameter
Wrong region:
- Confirm
CATAPULT_REGIONmatches your account:us,eu, orau