197 lines
6.5 KiB
Python
197 lines
6.5 KiB
Python
import asyncio
|
||
import os
|
||
import sys
|
||
from dotenv import load_dotenv
|
||
|
||
# Add src directory to path
|
||
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "src"))
|
||
|
||
from strava_mcp_server.strava_client import StravaClient
|
||
from mcp.server.fastmcp import Context
|
||
|
||
|
||
class MockContext(Context):
|
||
def __init__(self):
|
||
# Initialize parent with dummy request / info
|
||
pass
|
||
|
||
async def info(self, msg):
|
||
print(f"ℹ️ [INFO] {msg}")
|
||
|
||
async def warning(self, msg):
|
||
print(f"⚠️ [WARN] {msg}")
|
||
|
||
async def error(self, msg):
|
||
print(f"❌ [ERROR] {msg}")
|
||
|
||
|
||
async def generate():
|
||
from strava_mcp_server.config import get_config_file
|
||
|
||
# Load user config path
|
||
config_file = get_config_file()
|
||
if os.path.exists(config_file):
|
||
load_dotenv(dotenv_path=config_file)
|
||
|
||
# Fallback to local .env
|
||
load_dotenv()
|
||
|
||
client_id = os.getenv("STRAVA_CLIENT_ID")
|
||
client_secret = os.getenv("STRAVA_CLIENT_SECRET")
|
||
refresh_token = os.getenv("STRAVA_REFRESH_TOKEN")
|
||
|
||
if not all([client_id, client_secret, refresh_token]):
|
||
print(
|
||
"ℹ️ Missing Strava credentials in .env file. Generating a beautiful MOCK route (Alpe d'Huez, France) for testing!"
|
||
)
|
||
|
||
# A real encoded polyline representing the famous Alpe d'Huez climb in France!
|
||
mock_polyline = (
|
||
"y~yxFn{_e@_AmAcAaBo@sAGo@Gs@H_ANc@jAyB^kALu@D_@Fk@KqBIkACe@DuABsAd@sBHi@@e@KoAKo@eAsC"
|
||
"e@oAMa@Bs@Jc@Nk@bAgBv@wAd@{AJc@Ds@Fk@GqAMmAEc@ByA@uAb@sBDi@@e@KoAKo@eAsCe@oAMa@Bs@Jc@Nk"
|
||
"@bAgBv@wAd@{AJc@Ds@Fk@GqAMmAEc@ByA@uAb@sBDi@@e@KoAKo@eAsCe@oAMa@Bs@Jc@Nk@bAgBv@wAd@{"
|
||
"AJc@Ds@Fk@GqAMmAEc@ByA@oB`@cCDq@@e@IoAKo@eAsCe@oAMi@FkADqAd@sBHi@@e@KoAKo@eAsCe@oAM"
|
||
)
|
||
|
||
# Import the tool function
|
||
from strava_mcp_server.tools.activities import register
|
||
|
||
class MockMCP:
|
||
def __init__(self):
|
||
self.tools = {}
|
||
self.resources = {}
|
||
|
||
def tool(self):
|
||
def decorator(func):
|
||
self.tools[func.__name__] = func
|
||
return func
|
||
|
||
return decorator
|
||
|
||
def resource(self, path):
|
||
def decorator(func):
|
||
self.resources[path] = func
|
||
return func
|
||
|
||
return decorator
|
||
|
||
mcp = MockMCP()
|
||
|
||
# Mock StravaClient that returns our mock activity and streams
|
||
class MockClient:
|
||
async def get_activity(self, activity_id):
|
||
return {
|
||
"name": "🏔️ Alpe d'Huez Climb (Mock Test)",
|
||
"map": {"polyline": mock_polyline},
|
||
"distance": 14000,
|
||
"moving_time": 3600,
|
||
"total_elevation_gain": 1110,
|
||
"average_speed": 3.88,
|
||
"average_heartrate": 165,
|
||
"calories": 950,
|
||
}
|
||
|
||
async def get_activity_streams(self, activity_id, keys):
|
||
# Generate Alpe d'Huez climbing curve from 740m to 1850m
|
||
return [
|
||
{"type": "distance", "data": [i * 100 for i in range(141)]},
|
||
{
|
||
"type": "altitude",
|
||
"data": [
|
||
740 + int(1110 * (i / 140) ** 0.75) for i in range(141)
|
||
],
|
||
},
|
||
{
|
||
"type": "latlng",
|
||
"data": [
|
||
[45.1105 + (i * 0.0001), 6.0375 + (i * 0.0001)]
|
||
for i in range(141)
|
||
],
|
||
},
|
||
]
|
||
|
||
client = MockClient()
|
||
register(mcp, client)
|
||
|
||
print("Generating mock map HTML from resource...")
|
||
resource_handler = mcp.resources["strava://activity/{activity_id}/map"]
|
||
resource_result = await resource_handler(99999)
|
||
html_text = resource_result.contents[0].text
|
||
|
||
if html_text:
|
||
output_path = os.path.join(os.path.dirname(__file__), "..", "test.html")
|
||
with open(output_path, "w", encoding="utf-8") as f:
|
||
f.write(html_text)
|
||
print(f"✅ Success! Mock map written to '{os.path.abspath(output_path)}'")
|
||
print(
|
||
"👉 Double-click 'test.html' or open it in your browser to view the interactive map!"
|
||
)
|
||
return
|
||
|
||
client = StravaClient()
|
||
|
||
print("Fetching recent activities to find one with GPS data...")
|
||
try:
|
||
activities = await client.list_activities(limit=20)
|
||
gps_activity = None
|
||
for a in activities:
|
||
map_data = a.get("map", {})
|
||
if map_data.get("summary_polyline") or map_data.get("polyline"):
|
||
gps_activity = a
|
||
break
|
||
|
||
if not gps_activity:
|
||
print(
|
||
"❌ No activity with GPS/polyline data found in your recent 20 activities."
|
||
)
|
||
return
|
||
|
||
activity_id = gps_activity["id"]
|
||
print(f"Found GPS Activity: '{gps_activity.get('name')}' (ID: {activity_id})")
|
||
|
||
# Import the tool function
|
||
from strava_mcp_server.tools.activities import register
|
||
|
||
class MockMCP:
|
||
def __init__(self):
|
||
self.tools = {}
|
||
self.resources = {}
|
||
|
||
def tool(self):
|
||
def decorator(func):
|
||
self.tools[func.__name__] = func
|
||
return func
|
||
|
||
return decorator
|
||
|
||
def resource(self, path):
|
||
def decorator(func):
|
||
self.resources[path] = func
|
||
return func
|
||
|
||
return decorator
|
||
|
||
mcp = MockMCP()
|
||
register(mcp, client)
|
||
|
||
print("Generating map HTML from resource...")
|
||
resource_handler = mcp.resources["strava://activity/{activity_id}/map"]
|
||
resource_result = await resource_handler(activity_id)
|
||
html_text = resource_result.contents[0].text
|
||
|
||
if html_text:
|
||
output_path = os.path.join(os.path.dirname(__file__), "..", "test.html")
|
||
with open(output_path, "w", encoding="utf-8") as f:
|
||
f.write(html_text)
|
||
print(f"✅ Success! Map written to '{os.path.abspath(output_path)}'")
|
||
print(
|
||
"👉 Double-click 'test.html' or open it in your browser to view the interactive map!"
|
||
)
|
||
|
||
except Exception as e:
|
||
print(f"❌ Failed: {str(e)}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(generate())
|