Skip to main content
Glama
varunneal

Spotify MCP Server

by varunneal

SpotifyPlaylist

Create, manage, and modify Spotify playlists by adding or removing tracks, updating details, and viewing playlist contents.

Instructions

Manage Spotify playlists. - get: Get a list of user's playlists. - get_tracks: Get tracks in a specific playlist. - add_tracks: Add tracks to a specific playlist. - remove_tracks: Remove tracks from a specific playlist. - change_details: Change details of a specific playlist. - create: Create a new playlist.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction to perform: 'get', 'get_tracks', 'add_tracks', 'remove_tracks', 'change_details', 'create'.
playlist_idNoID of the playlist to manage.
track_idsNoList of track IDs to add/remove.
nameNoName for the playlist (required for create and change_details).
descriptionNoDescription for the playlist.
publicNoWhether the playlist should be public (for create action).

Implementation Reference

  • Handler logic for executing the SpotifyPlaylist tool. Dispatches based on 'action' parameter to various playlist operations by calling corresponding methods on spotify_client.
    case "Playlist":
        logger.info(f"Playlist operation with arguments: {arguments}")
        action = arguments.get("action")
        match action:
            case "get":
                logger.info(f"Getting current user's playlists with arguments: {arguments}")
                playlists = spotify_client.get_current_user_playlists()
                return [types.TextContent(
                    type="text",
                    text=json.dumps(playlists, indent=2)
                )]
            case "get_tracks":
                logger.info(f"Getting tracks in playlist with arguments: {arguments}")
                if not arguments.get("playlist_id"):
                    logger.error("playlist_id is required for get_tracks action.")
                    return [types.TextContent(
                        type="text",
                        text="playlist_id is required for get_tracks action."
                    )]
                tracks = spotify_client.get_playlist_tracks(arguments.get("playlist_id"))
                return [types.TextContent(
                    type="text",
                    text=json.dumps(tracks, indent=2)
                )]
            case "add_tracks":
                logger.info(f"Adding tracks to playlist with arguments: {arguments}")
                track_ids = arguments.get("track_ids")
                if isinstance(track_ids, str):
                    try:
                        track_ids = json.loads(track_ids)  # Convert JSON string to Python list
                    except json.JSONDecodeError:
                        logger.error("track_ids must be a list or a valid JSON array.")
                        return [types.TextContent(
                            type="text",
                            text="Error: track_ids must be a list or a valid JSON array."
                        )]
    
                spotify_client.add_tracks_to_playlist(
                    playlist_id=arguments.get("playlist_id"),
                    track_ids=track_ids
                )
                return [types.TextContent(
                    type="text",
                    text="Tracks added to playlist."
                )]
            case "remove_tracks":
                logger.info(f"Removing tracks from playlist with arguments: {arguments}")
                track_ids = arguments.get("track_ids")
                if isinstance(track_ids, str):
                    try:
                        track_ids = json.loads(track_ids)  # Convert JSON string to Python list
                    except json.JSONDecodeError:
                        logger.error("track_ids must be a list or a valid JSON array.")
                        return [types.TextContent(
                            type="text",
                            text="Error: track_ids must be a list or a valid JSON array."
                        )]
    
                spotify_client.remove_tracks_from_playlist(
                    playlist_id=arguments.get("playlist_id"),
                    track_ids=track_ids
                )
                return [types.TextContent(
                    type="text",
                    text="Tracks removed from playlist."
                )]
    
            case "change_details":
                logger.info(f"Changing playlist details with arguments: {arguments}")
                if not arguments.get("playlist_id"):
                    logger.error("playlist_id is required for change_details action.")
                    return [types.TextContent(
                        type="text",
                        text="playlist_id is required for change_details action."
                    )]
                if not arguments.get("name") and not arguments.get("description"):
                    logger.error("At least one of name, description or public is required.")
                    return [types.TextContent(
                        type="text",
                        text="At least one of name, description, public, or collaborative is required."
                    )]
    
                spotify_client.change_playlist_details(
                    playlist_id=arguments.get("playlist_id"),
                    name=arguments.get("name"),
                    description=arguments.get("description")
                )
                return [types.TextContent(
                    type="text",
                    text="Playlist details changed."
                )]
    
            case "create":
                logger.info(f"Creating playlist with arguments: {arguments}")
                if not arguments.get("name"):
                    logger.error("name is required for create action.")
                    return [types.TextContent(
                        type="text",
                        text="name is required for create action."
                    )]
                
                playlist = spotify_client.create_playlist(
                    name=arguments.get("name"),
                    description=arguments.get("description"),
                    public=arguments.get("public", True)
                )
                return [types.TextContent(
                    type="text",
                    text=json.dumps(playlist, indent=2)
                )]
    
            case _:
                return [types.TextContent(
                    type="text",
                    text=f"Unknown playlist action: {action}."
                         "Supported actions are: get, get_tracks, add_tracks, remove_tracks, change_details, create."
                )]
    case _:
  • Pydantic model defining the input schema for the SpotifyPlaylist tool, used to generate the tool schema.
    class Playlist(ToolModel):
        """Manage Spotify playlists.
        - get: Get a list of user's playlists.
        - get_tracks: Get tracks in a specific playlist.
        - add_tracks: Add tracks to a specific playlist.
        - remove_tracks: Remove tracks from a specific playlist.
        - change_details: Change details of a specific playlist.
        - create: Create a new playlist.
        """
        action: str = Field(
            description="Action to perform: 'get', 'get_tracks', 'add_tracks', 'remove_tracks', 'change_details', 'create'.")
        playlist_id: Optional[str] = Field(default=None, description="ID of the playlist to manage.")
        track_ids: Optional[List[str]] = Field(default=None, description="List of track IDs to add/remove.")
        name: Optional[str] = Field(default=None, description="Name for the playlist (required for create and change_details).")
        description: Optional[str] = Field(default=None, description="Description for the playlist.")
        public: Optional[bool] = Field(default=True, description="Whether the playlist should be public (for create action).")
  • Registers the SpotifyPlaylist tool (as Playlist.as_tool()) in the list of available tools returned to MCP clients.
    @server.list_tools()
    async def handle_list_tools() -> list[types.Tool]:
        """List available tools."""
        logger.info("Listing available tools")
        # await server.request_context.session.send_notification("are you recieving this notification?")
        tools = [
            Playback.as_tool(),
            Search.as_tool(),
            Queue.as_tool(),
            GetInfo.as_tool(),
            Playlist.as_tool(),
        ]
        logger.info(f"Available tools: {[tool.name for tool in tools]}")
        return tools
  • Helper method to retrieve the current user's playlists, called by 'get' action.
    def get_current_user_playlists(self, limit=50) -> List[Dict]:
        """
        Get current user's playlists.
        - limit: Max number of playlists to return.
        """
        playlists = self.sp.current_user_playlists()
        if not playlists:
            raise ValueError("No playlists found.")
        return [utils.parse_playlist(playlist, self.username) for playlist in playlists['items']]
  • Helper method to create a new playlist, called by 'create' action in the handler.
    def create_playlist(self, name: str, description: Optional[str] = None, public: bool = True):
        """
        Create a new playlist.
        - name: Name for the playlist.
        - description: Description for the playlist.
        - public: Whether the playlist should be public.
        """
        if not name:
            raise ValueError("Playlist name is required.")
        
        try:
            user = self.sp.current_user()
            user_id = user['id']
            
            playlist = self.sp.user_playlist_create(
                user=user_id,
                name=name,
                public=public,
                description=description
            )
            self.logger.info(f"Created playlist: {name} (ID: {playlist['id']})")
            return utils.parse_playlist(playlist, self.username, detailed=True)
        except Exception as e:
            self.logger.error(f"Error creating playlist: {str(e)}")
            raise
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. While it lists six actions and their basic purposes, it doesn't disclose critical behavioral traits: authentication requirements, rate limits, whether operations are destructive, what happens on errors, or what the return values look like. For a multi-action tool with potential write operations (add, remove, change, create), this is a significant gap in transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured with a brief overview followed by a bulleted list of actions. Each bullet is concise and to the point. However, the opening 'Manage Spotify playlists' is redundant with the tool name and could be eliminated, and the bullet format while clear isn't the most natural language for AI comprehension.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex multi-action tool with six parameters and no annotations or output schema, the description is incomplete. It doesn't explain return values, error conditions, authentication requirements, or the relationships between actions. The tool handles both read and write operations, but the description provides minimal behavioral context, making it inadequate for safe and effective use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all six parameters thoroughly. The description doesn't add any meaningful parameter semantics beyond what's in the schema - it simply lists action names without explaining parameter dependencies or constraints. The baseline of 3 is appropriate when the schema does all the parameter documentation work.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states 'Manage Spotify playlists' which is a vague purpose that doesn't specify what management entails. It then lists six sub-actions with brief explanations, but the overall purpose remains broad. While it distinguishes from sibling tools by focusing on playlists rather than search, playback, or queue operations, the verb 'manage' is too generic for clear understanding.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided about when to use this tool versus the sibling tools (SpotifyGetInfo, SpotifyPlayback, SpotifyQueue, SpotifySearch). The description lists six actions but doesn't explain when each should be used relative to each other or to other tools. There's no mention of prerequisites, authentication requirements, or contextual factors that would guide selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/varunneal/spotify-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server