Skip to content

Commit e586b21

Browse files
committed
Refactor and improve project structure
- Update Python version to 3.12 - Add linting and type checking with ruff and mypy - Remove strava_api.py and consolidate functionality - Update README to remove deprecated features - Improve type hints and code quality in server.py - Add GitHub Actions workflow for CI - Bump version to 0.1.3
1 parent c90a588 commit e586b21

File tree

9 files changed

+232
-494
lines changed

9 files changed

+232
-494
lines changed

.github/workflows/python-package.yml

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Python Package
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v3
14+
- name: Set up Python 3.12
15+
uses: actions/setup-python@v4
16+
with:
17+
python-version: "3.12"
18+
- name: Install dependencies
19+
run: |
20+
python -m pip install --upgrade pip
21+
python -m pip install ruff mypy
22+
pip install -e .
23+
- name: Lint with ruff
24+
run: |
25+
ruff check .
26+
- name: Type check with mypy
27+
run: |
28+
mypy src

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
.env
2+
src/strava_mcp_server/__pycache__/server.cpython-312.pyc

README.md

-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ The server exposes the following tools:
1616
- `get_activities_by_date_range(start_date: str, end_date: str, limit: int = 30)`: Get activities within a specific date range
1717
- `get_activity_by_id(activity_id: int)`: Get detailed information about a specific activity
1818
- `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
2019

2120
Dates should be provided in ISO format (`YYYY-MM-DD`).
2221

@@ -26,7 +25,6 @@ The server returns activity data with consistent field names and units:
2625

2726
| Field | Description | Unit |
2827
|-------|-------------|------|
29-
| `id` | Activity ID | - |
3028
| `name` | Activity name | - |
3129
| `sport_type` | Type of sport | - |
3230
| `start_date` | Start date and time | ISO 8601 |
@@ -101,7 +99,6 @@ Once connected, you can ask Claude questions like:
10199
- "Show me my activities from last week"
102100
- "What was my longest run in the past month?"
103101
- "Get details about my latest cycling activity"
104-
- "What's my athlete profile information?"
105102

106103
## Error Handling
107104

get_strava_token.py

+59-30
Original file line numberDiff line numberDiff line change
@@ -7,115 +7,144 @@
77

88
import os
99
import sys
10-
import json
11-
import requests
10+
from typing import Any
11+
12+
import requests # type: ignore
1213
from dotenv import load_dotenv
1314

14-
def print_auth_url(client_id):
15-
"""Print the authorization URL for the user to visit."""
15+
16+
def print_auth_url(client_id: str) -> None:
17+
"""
18+
Print the authorization URL for the user to visit.
19+
20+
Args:
21+
client_id: The Strava API client ID
22+
"""
1623
auth_url = (
1724
f"https://www.strava.com/oauth/authorize"
1825
f"?client_id={client_id}"
1926
f"&redirect_uri=http://localhost"
2027
f"&response_type=code"
2128
f"&scope=read,activity:read,activity:read_all,profile:read_all"
2229
)
23-
30+
2431
print("\n=== Step 1: Visit the following URL in your browser ===")
2532
print(auth_url)
2633
print("\nAuthorize the application when prompted.")
2734
print("You'll be redirected to a URL like: http://localhost/?state=&code=AUTHORIZATION_CODE&scope=...")
2835
print("Copy the 'code' parameter value from the URL.\n")
2936

30-
def exchange_code_for_token(client_id, client_secret, auth_code):
31-
"""Exchange the authorization code for tokens."""
37+
38+
def exchange_code_for_token(client_id: str, client_secret: str, auth_code: str) -> dict[str, Any]:
39+
"""
40+
Exchange the authorization code for tokens.
41+
42+
Args:
43+
client_id: The Strava API client ID
44+
client_secret: The Strava API client secret
45+
auth_code: The authorization code from the redirect URL
46+
47+
Returns:
48+
dictionary containing token data (access_token, refresh_token, expires_at)
49+
50+
Raises:
51+
SystemExit: If the token exchange fails
52+
"""
3253
token_url = "https://www.strava.com/oauth/token"
3354
payload = {
3455
'client_id': client_id,
3556
'client_secret': client_secret,
3657
'code': auth_code,
3758
'grant_type': 'authorization_code'
3859
}
39-
60+
4061
try:
4162
response = requests.post(token_url, data=payload)
4263
response.raise_for_status()
43-
return response.json()
64+
return dict(response.json())
4465
except requests.exceptions.RequestException as e:
4566
print(f"Error exchanging code for token: {e}")
4667
if hasattr(e, 'response') and e.response:
4768
print(f"Response: {e.response.text}")
4869
sys.exit(1)
4970

50-
def update_env_file(token_data):
51-
"""Update the .env file with the token data."""
71+
72+
def update_env_file(token_data: dict[str, Any]) -> None:
73+
"""
74+
Update the .env file with the token data.
75+
76+
Args:
77+
token_data: dictionary containing token data (access_token, refresh_token, expires_at)
78+
"""
5279
env_file = ".env"
53-
80+
5481
# Read existing .env file if it exists
55-
env_vars = {}
82+
env_vars: dict[str, str] = {}
5683
if os.path.exists(env_file):
5784
with open(env_file, 'r') as f:
5885
for line in f:
5986
line = line.strip()
6087
if line and not line.startswith('#') and '=' in line:
6188
key, value = line.split('=', 1)
6289
env_vars[key] = value
63-
90+
6491
# Update with new token data
6592
env_vars['STRAVA_REFRESH_TOKEN'] = token_data['refresh_token']
6693
env_vars['STRAVA_ACCESS_TOKEN'] = token_data['access_token']
6794
env_vars['STRAVA_EXPIRES_AT'] = str(token_data['expires_at'])
68-
95+
6996
# Write back to .env file
7097
with open(env_file, 'w') as f:
7198
for key, value in env_vars.items():
7299
f.write(f"{key}={value}\n")
73-
100+
74101
print(f"Updated {env_file} with new token data")
75102

76-
def main():
103+
104+
def main() -> None:
77105
"""Main function to guide the user through the token acquisition process."""
78106
load_dotenv()
79-
107+
80108
print("=== Strava API Token Helper ===")
81-
109+
82110
# Get client ID and secret
83111
client_id = os.environ.get('STRAVA_CLIENT_ID')
84112
client_secret = os.environ.get('STRAVA_CLIENT_SECRET')
85-
113+
86114
if not client_id or not client_secret:
87115
print("Please provide your Strava API credentials:")
88116
client_id = input("Client ID: ").strip()
89117
client_secret = input("Client Secret: ").strip()
90-
118+
91119
# Save to .env file
92120
with open(".env", 'w') as f:
93121
f.write(f"STRAVA_CLIENT_ID={client_id}\n")
94122
f.write(f"STRAVA_CLIENT_SECRET={client_secret}\n")
95-
123+
96124
print("Saved credentials to .env file")
97-
125+
98126
# Print authorization URL
99127
print_auth_url(client_id)
100-
128+
101129
# Get authorization code from user
102130
auth_code = input("Enter the authorization code from the URL: ").strip()
103-
131+
104132
# Exchange code for token
105133
print("\n=== Step 2: Exchanging code for token ===")
106134
token_data = exchange_code_for_token(client_id, client_secret, auth_code)
107-
135+
108136
# Print token information
109137
print("\n=== Token Information ===")
110138
print(f"Access Token: {token_data['access_token'][:10]}...")
111139
print(f"Refresh Token: {token_data['refresh_token'][:10]}...")
112140
print(f"Expires At: {token_data['expires_at']} (Unix timestamp)")
113-
141+
114142
# Update .env file
115143
update_env_file(token_data)
116-
144+
117145
print("\n=== Success! ===")
118-
print("You can now run the strava_api.py script to fetch your data.")
146+
print("You can now run the strava-mcp-server to access your Strava data.")
147+
119148

120149
if __name__ == "__main__":
121-
main()
150+
main()

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "strava-mcp-server"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
description = "MCP server for Strava API integration"
55
readme = "README.md"
66
authors = [
Binary file not shown.

0 commit comments

Comments
 (0)