Skip to main content
Glama
kylestratis

Spotify Playlist MCP Server

by kylestratis

spotify_get_audio_features

Read-onlyIdempotent

Retrieve detailed audio analysis features for Spotify tracks, including energy, tempo, danceability, and other sonic characteristics to power similarity matching and playlist creation.

Instructions

Get detailed audio analysis features for one or more Spotify tracks.

Retrieves sonic characteristics (energy, tempo, danceability, valence, acousticness, etc.)
that power the similarity engine. Supports batch processing of up to 100 tracks.

Args:
    - track_ids: List of Spotify track IDs, 1-100 tracks
    - response_format: 'markdown' or 'json' (default: JSON)

Returns:
    Markdown: Per-track audio features (acousticness, danceability, energy, instrumentalness, liveness, loudness, speechiness, valence, tempo, key, mode, time_signature)
    JSON: {"track_count": N, "features": [{id, acousticness, danceability, energy, instrumentalness, liveness, loudness, speechiness, valence, tempo, key, mode, time_signature, duration_ms, analysis_url, track_href, type, uri}]}

Examples:
    - "Analyze the audio features of this track" -> Get sonic characteristics
    - "What's the tempo and energy of these songs?" -> Extract specific features

Errors: Returns "No audio features available" if not found, or error for auth failure (401), rate limits (429). Note: Audio features endpoint deprecated for NEW apps (Nov 2024), but existing apps with extended mode access can still use it.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
paramsYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • server.py:579-588 (registration)
    Registers the spotify_get_audio_features tool using the @mcp.tool decorator with appropriate annotations.
    @mcp.tool(
        name="spotify_get_audio_features",
        annotations={
            "title": "Get Spotify Audio Features",
            "readOnlyHint": True,
            "destructiveHint": False,
            "idempotentHint": True,
            "openWorldHint": True,
        },
    )
  • The main handler function that fetches audio features using the helper function and formats the output in markdown or JSON based on the input parameters.
    async def spotify_get_audio_features(params: GetAudioFeaturesInput) -> str:
        """Get detailed audio analysis features for one or more Spotify tracks.
    
        Retrieves sonic characteristics (energy, tempo, danceability, valence, acousticness, etc.)
        that power the similarity engine. Supports batch processing of up to 100 tracks.
    
        Args:
            - track_ids: List of Spotify track IDs, 1-100 tracks
            - response_format: 'markdown' or 'json' (default: JSON)
    
        Returns:
            Markdown: Per-track audio features (acousticness, danceability, energy, instrumentalness, liveness, loudness, speechiness, valence, tempo, key, mode, time_signature)
            JSON: {"track_count": N, "features": [{id, acousticness, danceability, energy, instrumentalness, liveness, loudness, speechiness, valence, tempo, key, mode, time_signature, duration_ms, analysis_url, track_href, type, uri}]}
    
        Examples:
            - "Analyze the audio features of this track" -> Get sonic characteristics
            - "What's the tempo and energy of these songs?" -> Extract specific features
    
        Errors: Returns "No audio features available" if not found, or error for auth failure (401), rate limits (429). Note: Audio features endpoint deprecated for NEW apps (Nov 2024), but existing apps with extended mode access can still use it.
        """
        try:
            features_map = await get_audio_features_for_tracks(params.track_ids)
    
            if not features_map:
                return "No audio features available for the provided track IDs."
    
            if params.response_format == ResponseFormat.MARKDOWN:
                lines = ["# Audio Features\n"]
    
                for track_id, features in features_map.items():
                    lines.append(f"## Track: {track_id}")
                    lines.append(
                        f"- **Acousticness**: {features.get('acousticness', 0):.3f}"
                    )
                    lines.append(
                        f"- **Danceability**: {features.get('danceability', 0):.3f}"
                    )
                    lines.append(f"- **Energy**: {features.get('energy', 0):.3f}")
                    lines.append(
                        f"- **Instrumentalness**: {features.get('instrumentalness', 0):.3f}"
                    )
                    lines.append(f"- **Liveness**: {features.get('liveness', 0):.3f}")
                    lines.append(f"- **Loudness**: {features.get('loudness', 0):.1f} dB")
                    lines.append(f"- **Speechiness**: {features.get('speechiness', 0):.3f}")
                    lines.append(f"- **Valence**: {features.get('valence', 0):.3f}")
                    lines.append(f"- **Tempo**: {features.get('tempo', 0):.1f} BPM")
                    lines.append(f"- **Key**: {features.get('key', -1)}")
                    lines.append(
                        f"- **Mode**: {'Major' if features.get('mode') == 1 else 'Minor'}"
                    )
                    lines.append(
                        f"- **Time Signature**: {features.get('time_signature', 4)}/4\n"
                    )
    
                return "\n".join(lines)
            else:
                # JSON format
                return json.dumps(
                    {
                        "track_count": len(features_map),
                        "features": list(features_map.values()),
                    },
                    indent=2,
                )
    
        except Exception as e:
            return handle_spotify_error(e)
  • Pydantic BaseModel defining the input schema for the tool, validating track_ids (1-100) and response_format.
    class GetAudioFeaturesInput(BaseModel):
        """Input model for getting audio features."""
    
        model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)
    
        track_ids: list[str] = Field(
            ...,
            description="List of Spotify track IDs (1-100)",
            min_length=1,
            max_length=100,
        )
        response_format: ResponseFormat = Field(
            default=ResponseFormat.JSON,
            description="Output format: 'markdown' or 'json'",
        )
  • Helper function that retrieves audio features from the Spotify API for a list of track IDs, handling batch requests up to 100 tracks and fallback to individual requests if needed.
    async def get_audio_features_for_tracks(
        track_ids: list[str],
    ) -> dict[str, dict[str, Any]]:
        """Get audio features for multiple tracks.
    
        Args:
            track_ids: List of Spotify track IDs
    
        Returns:
            Dictionary mapping track IDs to their audio features
        """
        features_map = {}
    
        # Spotify API supports up to 100 tracks at once
        for i in range(0, len(track_ids), 100):
            batch = track_ids[i : i + 100]
            ids_param = ",".join(batch)
    
            try:
                data = await make_spotify_request(f"audio-features?ids={ids_param}")
                audio_features_list = data.get("audio_features", [])
    
                for features in audio_features_list:
                    if features:  # Skip None values for unavailable tracks
                        features_map[features["id"]] = features
            except Exception:
                # If batch fails, try individual requests
                for track_id in batch:
                    try:
                        features = await make_spotify_request(f"audio-features/{track_id}")
                        features_map[track_id] = features
                    except Exception:
                        pass  # Skip tracks that fail
    
        return features_map
Behavior4/5

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

Annotations already provide readOnlyHint=true, destructiveHint=false, openWorldHint=true, and idempotentHint=true. The description adds valuable context beyond this: it specifies batch processing limits (up to 100 tracks), error handling (e.g., 'No audio features available', auth failure, rate limits), and a deprecation note for new apps. This enhances behavioral understanding without contradicting annotations.

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 well-structured with sections for purpose, args, returns, examples, and errors, making it front-loaded and easy to scan. It is appropriately sized, but some parts like the detailed return formats could be slightly condensed. Overall, most sentences earn their place, though minor verbosity exists.

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

Completeness5/5

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

Given the complexity (batch processing, multiple output formats, deprecation notes) and the presence of an output schema (which covers return values), the description is complete enough. It includes purpose, usage examples, parameter details, error handling, and behavioral context, providing all necessary information for an agent to use the tool effectively without redundancy.

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 0%, but the description compensates by detailing track_ids (list of 1-100 Spotify track IDs) and response_format (options and default). However, it does not fully explain the semantics of each parameter beyond what's implied, such as the format of track IDs or deeper meaning of response_format choices. Given the low schema coverage, the description adds some value but could be more comprehensive.

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

Purpose5/5

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

The description clearly states the verb 'Get' and resource 'detailed audio analysis features for one or more Spotify tracks', specifying it retrieves sonic characteristics. It distinguishes from siblings like spotify_get_track (which gets track metadata) or spotify_find_similar_tracks (which finds recommendations), making the purpose specific and differentiated.

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

Usage Guidelines4/5

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

The description provides clear context for when to use it (e.g., 'Analyze the audio features of this track' or 'What's the tempo and energy of these songs?'), but does not explicitly state when not to use it or name alternatives among siblings. It implies usage for batch processing of up to 100 tracks, which is helpful but lacks explicit exclusions.

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/kylestratis/spotify-mcp'

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