Initial commit: Modularized Strava MCP Server with UV and Hatchling
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
Strava OAuth2 Authorization Helper
|
||||
|
||||
This script guides you through the Strava OAuth2 flow to obtain a refresh token
|
||||
with the correct scopes for the MCP server.
|
||||
|
||||
Required scopes:
|
||||
- read → basic athlete profile
|
||||
- activity:read → read your activities
|
||||
- activity:read_all → read private activities (optional)
|
||||
|
||||
Usage: uv run get_token.py
|
||||
"""
|
||||
import os
|
||||
import webbrowser
|
||||
import httpx
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
CLIENT_ID = os.getenv("STRAVA_CLIENT_ID")
|
||||
CLIENT_SECRET = os.getenv("STRAVA_CLIENT_SECRET")
|
||||
REDIRECT_URI = "http://localhost:8765/callback"
|
||||
SCOPES = "profile:read_all,activity:read_all,activity:read,profile:write"
|
||||
|
||||
# Global to capture the auth code from the callback
|
||||
auth_code: str | None = None
|
||||
|
||||
|
||||
class CallbackHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
global auth_code
|
||||
parsed = urlparse(self.path)
|
||||
params = parse_qs(parsed.query)
|
||||
|
||||
if "code" in params:
|
||||
auth_code = params["code"][0]
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html")
|
||||
self.end_headers()
|
||||
self.wfile.write(b"""
|
||||
<html><body style="font-family:sans-serif;text-align:center;padding:40px">
|
||||
<h2>✅ Authorization successful!</h2>
|
||||
<p>You can close this window and return to your terminal.</p>
|
||||
</body></html>
|
||||
""")
|
||||
else:
|
||||
error = params.get("error", ["unknown"])[0]
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "text/html")
|
||||
self.end_headers()
|
||||
self.wfile.write(f"<html><body><h2>❌ Error: {error}</h2></body></html>".encode())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass # Suppress server logs
|
||||
|
||||
|
||||
def main():
|
||||
if not CLIENT_ID or not CLIENT_SECRET:
|
||||
print("❌ Missing STRAVA_CLIENT_ID or STRAVA_CLIENT_SECRET in .env")
|
||||
return
|
||||
|
||||
auth_url = (
|
||||
f"https://www.strava.com/oauth/authorize"
|
||||
f"?client_id={CLIENT_ID}"
|
||||
f"&redirect_uri={REDIRECT_URI}"
|
||||
f"&response_type=code"
|
||||
f"&approval_prompt=force"
|
||||
f"&scope={SCOPES}"
|
||||
)
|
||||
|
||||
print("=" * 60)
|
||||
print(" Strava OAuth2 Authorization")
|
||||
print("=" * 60)
|
||||
print(f"\nRequesting scopes: {SCOPES}\n")
|
||||
print("Opening Strava in your browser...")
|
||||
print("If the browser doesn't open, visit this URL manually:\n")
|
||||
print(f" {auth_url}\n")
|
||||
|
||||
webbrowser.open(auth_url)
|
||||
|
||||
print("Waiting for callback on http://localhost:8765 ...")
|
||||
server = HTTPServer(("localhost", 8765), CallbackHandler)
|
||||
server.handle_request() # Handle exactly one request (the callback)
|
||||
|
||||
if not auth_code:
|
||||
print("❌ No authorization code received.")
|
||||
return
|
||||
|
||||
print("\nExchanging authorization code for tokens...")
|
||||
response = httpx.post(
|
||||
"https://www.strava.com/oauth/token",
|
||||
data={
|
||||
"client_id": CLIENT_ID,
|
||||
"client_secret": CLIENT_SECRET,
|
||||
"code": auth_code,
|
||||
"grant_type": "authorization_code",
|
||||
},
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"❌ Token exchange failed: {response.status_code} {response.text}")
|
||||
return
|
||||
|
||||
data = response.json()
|
||||
refresh_token = data["refresh_token"]
|
||||
athlete = data.get("athlete", {})
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(" ✅ Authorization successful!")
|
||||
print("=" * 60)
|
||||
print(f"\nAthlete: {athlete.get('firstname')} {athlete.get('lastname')}")
|
||||
print(f"Scopes granted: {data.get('scope', 'unknown')}\n")
|
||||
print("Add the following to your .env file:")
|
||||
print("-" * 40)
|
||||
print(f"STRAVA_CLIENT_ID={CLIENT_ID}")
|
||||
print(f"STRAVA_CLIENT_SECRET={CLIENT_SECRET}")
|
||||
print(f"STRAVA_REFRESH_TOKEN={refresh_token}")
|
||||
print("-" * 40)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user