Skip to content

Commit c90a588

Browse files
committed
First commit 🔥
0 parents  commit c90a588

13 files changed

+1418
-0
lines changed

Diff for: ‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env

Diff for: ‎.python-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.12

Diff for: ‎LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Tomek Korbak
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Diff for: ‎README.md

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Strava MCP Server
2+
3+
![Python Package](https://github.com/tomekkorbak/strava-mcp-server/workflows/Python%20Package/badge.svg)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5+
[![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)](https://www.python.org/downloads/release/python-3100/)
6+
7+
A [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) server that provides access to the Strava API. It allows language models to query athlete activities data from the Strava API.
8+
9+
## Available Tools
10+
11+
The server exposes the following tools:
12+
13+
### Activities Queries
14+
15+
- `get_activities(limit: int = 10)`: Get the authenticated athlete's recent activities
16+
- `get_activities_by_date_range(start_date: str, end_date: str, limit: int = 30)`: Get activities within a specific date range
17+
- `get_activity_by_id(activity_id: int)`: Get detailed information about a specific activity
18+
- `get_recent_activities(days: int = 7, limit: int = 10)`: Get activities from the past X days
19+
- `get_athlete_profile()`: Get the authenticated athlete's profile
20+
21+
Dates should be provided in ISO format (`YYYY-MM-DD`).
22+
23+
## Activity Data Format
24+
25+
The server returns activity data with consistent field names and units:
26+
27+
| Field | Description | Unit |
28+
|-------|-------------|------|
29+
| `id` | Activity ID | - |
30+
| `name` | Activity name | - |
31+
| `sport_type` | Type of sport | - |
32+
| `start_date` | Start date and time | ISO 8601 |
33+
| `distance_metres` | Distance | meters |
34+
| `elapsed_time_seconds` | Total elapsed time | seconds |
35+
| `moving_time_seconds` | Moving time | seconds |
36+
| `average_speed_mps` | Average speed | meters per second |
37+
| `max_speed_mps` | Maximum speed | meters per second |
38+
| `total_elevation_gain_metres` | Total elevation gain | meters |
39+
| `elev_high_metres` | Highest elevation | meters |
40+
| `elev_low_metres` | Lowest elevation | meters |
41+
| `calories` | Calories burned | kcal |
42+
| `start_latlng` | Start coordinates | [lat, lng] |
43+
| `end_latlng` | End coordinates | [lat, lng] |
44+
45+
## Authentication
46+
47+
To use this server, you'll need to authenticate with the Strava API. Follow these steps:
48+
49+
1. Create a Strava API application:
50+
- Go to [Strava API Settings](https://www.strava.com/settings/api)
51+
- Create an application to get your Client ID and Client Secret
52+
- Set the Authorization Callback Domain to `localhost`
53+
54+
2. Get your refresh token:
55+
- Use the included `get_strava_token.py` script:
56+
```bash
57+
python get_strava_token.py
58+
```
59+
- Follow the prompts to authorize your application
60+
- The script will save your tokens to a `.env` file
61+
62+
3. Set environment variables:
63+
The server requires the following environment variables:
64+
- `STRAVA_CLIENT_ID`: Your Strava API Client ID
65+
- `STRAVA_CLIENT_SECRET`: Your Strava API Client Secret
66+
- `STRAVA_REFRESH_TOKEN`: Your Strava API Refresh Token
67+
68+
## Usage
69+
70+
### Claude for Desktop
71+
72+
Update your `claude_desktop_config.json` (located in `~/Library/Application\ Support/Claude/claude_desktop_config.json` on macOS and `%APPDATA%/Claude/claude_desktop_config.json` on Windows) to include the following:
73+
74+
```json
75+
{
76+
"mcpServers": {
77+
"strava": {
78+
"command": "uvx",
79+
"args": [
80+
"strava-mcp-server"
81+
],
82+
"env": {
83+
"STRAVA_CLIENT_ID": "YOUR_CLIENT_ID",
84+
"STRAVA_CLIENT_SECRET": "YOUR_CLIENT_SECRET",
85+
"STRAVA_REFRESH_TOKEN": "YOUR_REFRESH_TOKEN"
86+
}
87+
}
88+
}
89+
}
90+
```
91+
92+
### Claude Web
93+
94+
For Claude Web, you can run the server locally and connect it using the MCP extension.
95+
96+
## Example Queries
97+
98+
Once connected, you can ask Claude questions like:
99+
100+
- "What are my recent activities?"
101+
- "Show me my activities from last week"
102+
- "What was my longest run in the past month?"
103+
- "Get details about my latest cycling activity"
104+
- "What's my athlete profile information?"
105+
106+
## Error Handling
107+
108+
The server provides human-readable error messages for common issues:
109+
110+
- Invalid date formats
111+
- API authentication errors
112+
- Network connectivity problems
113+
114+
## License
115+
116+
This project is licensed under the MIT License - see the LICENSE file for details.

Diff for: ‎get_strava_token.py

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Strava Token Helper
4+
5+
This script helps you get a refresh token for the Strava API.
6+
"""
7+
8+
import os
9+
import sys
10+
import json
11+
import requests
12+
from dotenv import load_dotenv
13+
14+
def print_auth_url(client_id):
15+
"""Print the authorization URL for the user to visit."""
16+
auth_url = (
17+
f"https://www.strava.com/oauth/authorize"
18+
f"?client_id={client_id}"
19+
f"&redirect_uri=http://localhost"
20+
f"&response_type=code"
21+
f"&scope=read,activity:read,activity:read_all,profile:read_all"
22+
)
23+
24+
print("\n=== Step 1: Visit the following URL in your browser ===")
25+
print(auth_url)
26+
print("\nAuthorize the application when prompted.")
27+
print("You'll be redirected to a URL like: http://localhost/?state=&code=AUTHORIZATION_CODE&scope=...")
28+
print("Copy the 'code' parameter value from the URL.\n")
29+
30+
def exchange_code_for_token(client_id, client_secret, auth_code):
31+
"""Exchange the authorization code for tokens."""
32+
token_url = "https://www.strava.com/oauth/token"
33+
payload = {
34+
'client_id': client_id,
35+
'client_secret': client_secret,
36+
'code': auth_code,
37+
'grant_type': 'authorization_code'
38+
}
39+
40+
try:
41+
response = requests.post(token_url, data=payload)
42+
response.raise_for_status()
43+
return response.json()
44+
except requests.exceptions.RequestException as e:
45+
print(f"Error exchanging code for token: {e}")
46+
if hasattr(e, 'response') and e.response:
47+
print(f"Response: {e.response.text}")
48+
sys.exit(1)
49+
50+
def update_env_file(token_data):
51+
"""Update the .env file with the token data."""
52+
env_file = ".env"
53+
54+
# Read existing .env file if it exists
55+
env_vars = {}
56+
if os.path.exists(env_file):
57+
with open(env_file, 'r') as f:
58+
for line in f:
59+
line = line.strip()
60+
if line and not line.startswith('#') and '=' in line:
61+
key, value = line.split('=', 1)
62+
env_vars[key] = value
63+
64+
# Update with new token data
65+
env_vars['STRAVA_REFRESH_TOKEN'] = token_data['refresh_token']
66+
env_vars['STRAVA_ACCESS_TOKEN'] = token_data['access_token']
67+
env_vars['STRAVA_EXPIRES_AT'] = str(token_data['expires_at'])
68+
69+
# Write back to .env file
70+
with open(env_file, 'w') as f:
71+
for key, value in env_vars.items():
72+
f.write(f"{key}={value}\n")
73+
74+
print(f"Updated {env_file} with new token data")
75+
76+
def main():
77+
"""Main function to guide the user through the token acquisition process."""
78+
load_dotenv()
79+
80+
print("=== Strava API Token Helper ===")
81+
82+
# Get client ID and secret
83+
client_id = os.environ.get('STRAVA_CLIENT_ID')
84+
client_secret = os.environ.get('STRAVA_CLIENT_SECRET')
85+
86+
if not client_id or not client_secret:
87+
print("Please provide your Strava API credentials:")
88+
client_id = input("Client ID: ").strip()
89+
client_secret = input("Client Secret: ").strip()
90+
91+
# Save to .env file
92+
with open(".env", 'w') as f:
93+
f.write(f"STRAVA_CLIENT_ID={client_id}\n")
94+
f.write(f"STRAVA_CLIENT_SECRET={client_secret}\n")
95+
96+
print("Saved credentials to .env file")
97+
98+
# Print authorization URL
99+
print_auth_url(client_id)
100+
101+
# Get authorization code from user
102+
auth_code = input("Enter the authorization code from the URL: ").strip()
103+
104+
# Exchange code for token
105+
print("\n=== Step 2: Exchanging code for token ===")
106+
token_data = exchange_code_for_token(client_id, client_secret, auth_code)
107+
108+
# Print token information
109+
print("\n=== Token Information ===")
110+
print(f"Access Token: {token_data['access_token'][:10]}...")
111+
print(f"Refresh Token: {token_data['refresh_token'][:10]}...")
112+
print(f"Expires At: {token_data['expires_at']} (Unix timestamp)")
113+
114+
# Update .env file
115+
update_env_file(token_data)
116+
117+
print("\n=== Success! ===")
118+
print("You can now run the strava_api.py script to fetch your data.")
119+
120+
if __name__ == "__main__":
121+
main()

Diff for: ‎pyproject.toml

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
[project]
2+
name = "strava-mcp-server"
3+
version = "0.1.2"
4+
description = "MCP server for Strava API integration"
5+
readme = "README.md"
6+
authors = [
7+
{ name = "Tomek Korbak", email = "[email protected]" }
8+
]
9+
requires-python = ">=3.12"
10+
dependencies = [
11+
"httpx>=0.24.0",
12+
"python-dotenv>=1.0.0",
13+
"fastmcp>=0.1.0",
14+
"rich>=13.0.0",
15+
]
16+
classifiers = [
17+
"Development Status :: 4 - Beta",
18+
"Intended Audience :: Developers",
19+
"License :: OSI Approved :: MIT License",
20+
"Programming Language :: Python :: 3",
21+
"Programming Language :: Python :: 3.12",
22+
"Topic :: Software Development :: Libraries :: Python Modules",
23+
]
24+
25+
[build-system]
26+
requires = ["hatchling"]
27+
build-backend = "hatchling.build"
28+
29+
[project.scripts]
30+
strava-mcp-server = "strava_mcp_server:main"
31+
32+
[project.optional-dependencies]
33+
dev = [
34+
"ruff>=0.1.0",
35+
"mypy>=1.0.0",
36+
]
37+
38+
[project.urls]
39+
"Homepage" = "https://github.com/tomek-korbak/strava-mcp-server"
40+
"Bug Tracker" = "https://github.com/tomek-korbak/strava-mcp-server/issues"
41+
42+
[tool.hatch.build.targets.wheel]
43+
packages = ["src/strava_mcp_server"]
44+
45+
[tool.hatch.build]
46+
only-packages = true
47+
48+
[tool.ruff]
49+
line-length = 100
50+
target-version = "py312"
51+
select = ["E", "F", "I", "B", "W"]
52+
53+
[tool.mypy]
54+
python_version = "3.12"
55+
warn_return_any = true
56+
warn_unused_configs = true
57+
disallow_untyped_defs = true
58+
disallow_incomplete_defs = true

Diff for: ‎src/strava_mcp_server/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Strava MCP Server package."""
2+
3+
from strava_mcp_server.server import main
4+
5+
__all__ = ["main"]
6+
7+
def hello() -> str:
8+
return "Hello from strava-mcp-server!"
455 Bytes
Binary file not shown.
12.9 KB
Binary file not shown.

Diff for: ‎src/strava_mcp_server/py.typed

Whitespace-only changes.

0 commit comments

Comments
 (0)