Implement new MCP protocol features#14
Conversation
the server talks to the raw spotipy client directly, so the old Client wrapper and its helpers were never reached. ~1400 lines of unused code + their tests removed. - spotify_api: drop all Client methods except __init__ (auth/token setup) - utils: keep only normalize_redirect_uri; drop unused parse_* + validate - errors: drop to_mcp_error, the handle_spotify_error decorators, and unused validation_error/no_active_device/premium_required helpers - logging_utils: drop unused log_api_call and log_batch_operation - rewrite the affected tests; test_spotify_api no longer wraps assertions in try/except so it actually verifies behavior
replaces dict[str, Any] at the parse/model-building boundaries so mypy checks our key access against the actual Spotify object shapes. - new spotify_types module describing the subset of fields we read - type parse_track + the artist/album/playlist tools against them - add `from __future__ import annotations` so the type-only imports stay under TYPE_CHECKING
every tool now has at least one success path and one failure path; adds the previously untested tools and the resources/prompts. coverage 55% -> 95%. - cover get_artist_info, get_album_info, get_saved_tracks, remove_tracks_from_playlist, modify_playlist_details - add batch + over-limit cases for get_track_info, validation cases for modify_playlist_details, filtered-query + clamp cases for search - assert SpotifyException is translated to ValueError per tool - test the resources (current_user, current_playback) and the 3 prompts - add sample_artist_data / sample_album_data fixtures - drop the 3 empty placeholder tests
- start a changelog covering the 0.2.0 release, the dependency modernization, dead-code removal, and the coverage/type work
- define Followers before ArtistObject that references it - make name required on ArtistRef/AlbumRef (always present in the API) so mypy catches name-less access - use cast() instead of an assignment annotation in get_user_playlists to make the type assertion explicit - add a limit-clamp test for get_saved_tracks
…elicitation)
builds on the mcp 1.27.1 SDK to use protocol affordances the server wasn't
touching yet — all backward compatible.
- add tool annotations (readOnly/destructive/idempotent hints) + human titles + a shared spotify icon to all 15 tools
- give every tool a real output schema via pydantic result models (SearchResults, QueueState, TrackList, ArtistInfo, AlbumInfo, PlaylistList, PlaylistTracks, SavedTracks, ActionResult)
- stream progress + log notifications from get_playlist_tracks via Context for large playlists
- confirm destructive removals with ctx.elicit, falling through gracefully when the client lacks elicitation support
- keep the SpotifyMCPError code + suggestion in the surfaced message instead of flattening it
- add spotify://track|playlist|artist|album/{id} resource templates
- make log_tool_execution async-aware so async tools time correctly
- expand tests for the new return models, async tools, elicitation, progress, and resource templates
closes the real coverage gaps surfaced while reviewing the new mcp features — mocking stays at the spotipy boundary so the transforms run for real. - exercise multi-batch playlist pagination (offset advancement, remaining-count termination, short-page stop, empty playlist, total fallback) - cover elicitation accept/decline/unsupported and the accept-without-confirm cancel branch - add success + error paths for all four resource templates plus the playback resource error - cover the album and year_range search filters and non-track qtype conversion - cover modify_playlist_details with a description - cover the 403 playback-restricted, 404 user-not-found, and device-not-found error mappings
- only skip the removal-confirmation prompt when the client truly lacks elicitation support; if a capable client's prompt errors, propagate rather than silently deleting tracks - read playback from current_playback() so device/volume/shuffle/repeat are populated instead of always null - surface the snapshot_id returned by playlist add/remove - tolerate null entries in search result items - drop unused error codes left over from the deleted error helpers - refresh stale "returns a dict" docstrings to name the typed models
- document structured output, annotations/icons, progress, and elicitation in CHANGELOG, README, and CLAUDE.md - fix the stale "13 tools" count (now 11) and the "always return a dict" guidance (tools return typed pydantic models) - correct the core-files map: spotify_api is OAuth-only, add spotify_types, utils is just redirect normalization
# Conflicts: # CHANGELOG.md # src/spotify_mcp/errors.py # src/spotify_mcp/fastmcp_server.py # src/spotify_mcp/logging_utils.py # tests/test_fastmcp_tools.py
Code ReviewPR: Implement new MCP protocol features OverviewThis is a solid modernization pass that upgrades the server to MCP 1.27.1 features. The core changes — typed Pydantic output models, tool annotations, progress notifications, elicitation for destructive ops, and new resource templates — are well-motivated and cleanly executed. The diff is large but the structure is consistent throughout. What's Working Well
Issues🔴 Critical: Async tests may not be executingThe async test methods (e.g. class TestRemoveTracksFromPlaylist:
async def test_converts_ids_and_uris(self, mock_spotify_api):
...Without either
[tool.pytest.ini_options]
asyncio_mode = "auto"or decorate every async test with 🟡 Minor:
|
a big modernization pass plus new mcp protocol features for the spotify server. trims the codebase to the fastmcp essentials, types the spotify boundary, bumps deps, then uses mcp 1.27.1 features the server wasn't touching. all backward compatible.
dict[str, Any]returnsget_playlist_tracksviaContextfor large playlistsctx.elicit, falling through gracefully when the client doesn't support elicitationSpotifyMCPErrorcode + suggestion in the surfaced message instead of flattening itspotify://track|playlist|artist|album/{id}resource templatesto test:
uv run pytest→ 106 passuv run mypy src/→ clean