diff --git a/scripts/generate_test_map.py b/scripts/generate_test_map.py index 20e3386..346347f 100644 --- a/scripts/generate_test_map.py +++ b/scripts/generate_test_map.py @@ -8,7 +8,7 @@ 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 -from mcp.types import TextContent +from mcp.types import TextContent, EmbeddedResource class MockContext(Context): @@ -117,6 +117,12 @@ async def generate(): if isinstance(item, TextContent) and "" in item.text: html_text = item.text break + elif ( + isinstance(item, EmbeddedResource) + and item.resource.mimeType == "text/html" + ): + html_text = item.resource.text + break if html_text: output_path = os.path.join(os.path.dirname(__file__), "..", "test.html") @@ -178,6 +184,12 @@ async def generate(): if isinstance(item, TextContent) and "" in item.text: html_text = item.text break + elif ( + isinstance(item, EmbeddedResource) + and item.resource.mimeType == "text/html" + ): + html_text = item.resource.text + break if html_text: output_path = os.path.join(os.path.dirname(__file__), "..", "test.html") diff --git a/src/strava_mcp_server/tools/activities.py b/src/strava_mcp_server/tools/activities.py index c5c05ec..b5185e3 100644 --- a/src/strava_mcp_server/tools/activities.py +++ b/src/strava_mcp_server/tools/activities.py @@ -32,6 +32,19 @@ def _user_text(text: str) -> TextContent: ) +def _user_html_resource(uri: str, html: str) -> EmbeddedResource: + """Helper: return a user-facing EmbeddedResource with text/html.""" + return EmbeddedResource( + type="resource", + resource=TextResourceContents( + uri=uri, + mimeType="text/html", + text=html, + ), + annotations=Annotations(audience=["user"]), + ) + + def register(mcp: FastMCP, strava: StravaClient) -> None: @mcp.tool() async def list_activities( @@ -639,6 +652,23 @@ def register(mcp: FastMCP, strava: StravaClient) -> None: if (coordinates.length > 0) {{ track = L.polyline(coordinates, {{color: '#fc4c02', weight: 5, opacity: 0.8}}).addTo(map); map.fitBounds(track.getBounds()); + + // Start- und End-Marker zeichnen + var startIcon = L.divIcon({{ + html: '🟢', + iconSize: [18, 18], + iconAnchor: [9, 9], + className: 'start-marker-emoji' + }}); + L.marker(coordinates[0], {{ icon: startIcon, zIndexOffset: 500 }}).addTo(map); + + var endIcon = L.divIcon({{ + html: '🏁', + iconSize: [22, 22], + iconAnchor: [11, 20], + className: 'end-marker-emoji' + }}); + L.marker(coordinates[coordinates.length - 1], {{ icon: endIcon, zIndexOffset: 500 }}).addTo(map); }} else {{ map.setView([0, 0], 2); }} @@ -841,7 +871,10 @@ def register(mcp: FastMCP, strava: StravaClient) -> None: """ return [ - TextContent(type="text", text=html_content.strip()), + _user_html_resource( + f"internal://activities/{activity_id}/map.html", + html_content.strip(), + ), _resource( f"internal://activities/{activity_id}/map", {"polyline": polyline}, diff --git a/tests/unit/test_map.py b/tests/unit/test_map.py index c011e60..ddf9ae4 100644 --- a/tests/unit/test_map.py +++ b/tests/unit/test_map.py @@ -47,16 +47,22 @@ async def test_get_activity_map_success(): result = await get_activity_map(ctx, 12345) assert len(result) == 2 - # First block is the HTML text content - assert result[0].type == "text" - assert "" in result[0].text - assert "a~l~FqzbwOq}@it@..." in result[0].text - assert "unpkg.com/leaflet" in result[0].text - assert "latlngData" in result[0].text - assert "[47.1, 9.1]" in result[0].text - assert 'sportEmoji = "🚴"' in result[0].text - assert "L.control.layers" in result[0].text - assert "satellite" in result[0].text + # First block is the HTML embedded resource + assert result[0].type == "resource" + assert result[0].resource.mimeType == "text/html" + assert "internal://activities/12345/map.html" in str(result[0].resource.uri) + + html_text = result[0].resource.text + assert "" in html_text + assert "a~l~FqzbwOq}@it@..." in html_text + assert "unpkg.com/leaflet" in html_text + assert "latlngData" in html_text + assert "[47.1, 9.1]" in html_text + assert 'sportEmoji = "🚴"' in html_text + assert "L.control.layers" in html_text + assert "satellite" in html_text + assert "🟢" in html_text + assert "🏁" in html_text # Second block is the internal resource assert result[1].type == "resource"