feat: add direct HTTP custom route /get-map/{activity_id} using FastMCP decorator and bump version to 0.2.12
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
"""Strava MCP Server"""
|
||||
|
||||
__version__ = "0.2.11"
|
||||
__version__ = "0.2.12"
|
||||
|
||||
@@ -931,3 +931,20 @@ def register(mcp: FastMCP, strava: StravaClient) -> None:
|
||||
return await _render_activity_map_html(last_id)
|
||||
except Exception as e:
|
||||
return f"<html><body><p>Error loading last activity: {str(e)}</p></body></html>"
|
||||
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
@mcp.custom_route("/get-map/{activity_id}", methods=["GET"])
|
||||
async def get_web_map(request: Request) -> HTMLResponse:
|
||||
"""HTTP Endpoint to directly serve the interactive Leaflet HTML map."""
|
||||
activity_id_str = request.path_params.get("activity_id")
|
||||
try:
|
||||
activity_id = int(activity_id_str)
|
||||
html_content = await _render_activity_map_html(activity_id)
|
||||
return HTMLResponse(content=html_content)
|
||||
except Exception as e:
|
||||
return HTMLResponse(
|
||||
content=f"<html><body><h3>Error loading map:</h3><p>{str(e)}</p></body></html>",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
@@ -25,6 +25,16 @@ class MockMCP:
|
||||
|
||||
return decorator
|
||||
|
||||
def custom_route(self, path, methods, **kwargs):
|
||||
if not hasattr(self, "routes"):
|
||||
self.routes = {}
|
||||
|
||||
def decorator(func):
|
||||
self.routes[path] = func
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_activity_map_success():
|
||||
@@ -109,3 +119,45 @@ async def test_get_activity_map_no_polyline():
|
||||
assert len(result.content) == 1
|
||||
assert result.content[0].type == "text"
|
||||
assert "⚠️ Keine GPS-Routen-Daten" in result.content[0].text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_web_map_route_success():
|
||||
mcp = MockMCP()
|
||||
strava = MagicMock()
|
||||
|
||||
# Mock detailed activity with a valid polyline
|
||||
strava.get_activity = AsyncMock(
|
||||
return_value={
|
||||
"name": "Evening Ride",
|
||||
"map": {"polyline": "a~l~FqzbwOq}@it@..."},
|
||||
"sport_type": "Ride",
|
||||
}
|
||||
)
|
||||
strava.get_activity_streams = AsyncMock(
|
||||
return_value={
|
||||
"altitude": {"data": [100.0, 101.0, 102.0]},
|
||||
"distance": {"data": [0.0, 100.0, 200.0]},
|
||||
"latlng": {"data": [[47.1, 9.1], [47.2, 9.2], [47.3, 9.3]]},
|
||||
}
|
||||
)
|
||||
|
||||
register(mcp, strava)
|
||||
|
||||
assert "/get-map/{activity_id}" in mcp.routes
|
||||
route_handler = mcp.routes["/get-map/{activity_id}"]
|
||||
|
||||
# Mock Starlette Request
|
||||
request = MagicMock()
|
||||
request.path_params = {"activity_id": "12345"}
|
||||
|
||||
response = await route_handler(request)
|
||||
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
assert isinstance(response, HTMLResponse)
|
||||
assert response.status_code == 200
|
||||
|
||||
html_body = response.body.decode("utf-8")
|
||||
assert "<!DOCTYPE html>" in html_body
|
||||
assert "Evening Ride" in html_body
|
||||
|
||||
Reference in New Issue
Block a user