DEV Community

Daniela Oliveira
Daniela Oliveira

Posted on

Spotify Lyrics with Algolia MCP

🎧 Motivation Behind the Project

Modern music streaming services have evolved tremendously — yet, one area still feels incomplete: access to full and rich lyrics for all songs. This gap becomes more visible with indie artists, non-English songs, and unreleased demos, which often lack proper lyric integration.

While some platforms use services like Musixmatch, others such as Genius or Letras.mus.br host a broader variety of lyrical content. However, these platforms are not deeply integrated into most music players today.

This project started with a question:

What if we could enhance the music listening experience by integrating intelligent, fast, and respectful lyric search — using the tools we already have today?

Link:đź”—http://localhost:3000/api/auth/login

🔍 The Vision

The project was guided by two main ideas:

User experience in music apps should be contextual, complete, and intelligent.
Lyrics are a core part of how people connect with music. When they’re missing, the experience feels less immersive.

The limitation is not in technology — it’s in integration and licensing decisions.
We already have the tools to build this. What’s missing is the alignment between platforms to make it happen.

⚙️ The Solution

Using Algolia’s MCP Server for fast, scalable search and Google Gemini CLI for smart content handling, this project simulates how a user-friendly lyric search experience could work — one that connects streaming activity with external lyrics platforms, without hosting or displaying copyrighted material directly.

This prototype doesn’t display real-time lyrics (to respect legal boundaries), but it demonstrates the technical feasibility and opens a conversation on what could be built with the right partnerships and permissions.

const algoliasearch = require('algoliasearch');

class AlgoliaService {
  constructor() {
    this.client = algoliasearch(
      process.env.ALGOLIA_APP_ID,
      process.env.ALGOLIA_API_KEY
    );
    this.index = this.client.initIndex('lyrics');
  }

  async searchLyrics(artist, title) {
    try {
      const searchQuery = `${artist} ${title}`;

      const { hits } = await this.index.search(searchQuery, {
        attributesToRetrieve: ['artist', 'title', 'lyrics', 'source', 'url'],
        hitsPerPage: 5,
        typoTolerance: true,
        ignorePlurals: true
      });

      if (hits.length === 0) {
        return null;
      }

      // Find best match based on artist and title similarity
      const bestMatch = this.findBestMatch(hits, artist, title);

      if (bestMatch) {
        return {
          lyrics: bestMatch.lyrics,
          source: bestMatch.source || 'Algolia Index',
          confidence: bestMatch._score || 0.8,
          url: bestMatch.url,
          artist: bestMatch.artist,
          title: bestMatch.title
        };
      }

      return null;
    } catch (error) {
      console.error('Algolia search error:', error);
      return null;
    }
  }

  findBestMatch(hits, targetArtist, targetTitle) {
    let bestMatch = null;
    let highestScore = 0;

    for (const hit of hits) {
      const artistScore = this.calculateSimilarity(
        hit.artist?.toLowerCase() || '', 
        targetArtist.toLowerCase()
      );
      const titleScore = this.calculateSimilarity(
        hit.title?.toLowerCase() || '', 
        targetTitle.toLowerCase()
      );

      const totalScore = (artistScore + titleScore) / 2;

      if (totalScore > highestScore && totalScore > 0.6) {
        highestScore = totalScore;
        bestMatch = { ...hit, _score: totalScore };
      }
    }

    return bestMatch;
  }

  calculateSimilarity(str1, str2) {
    // Simple similarity calculation (Levenshtein distance based)
    const longer = str1.length > str2.length ? str1 : str2;
    const shorter = str1.length > str2.length ? str2 : str1;

    if (longer.length === 0) return 1.0;

    const distance = this.levenshteinDistance(longer, shorter);
    return (longer.length - distance) / longer.length;
  }

  levenshteinDistance(str1, str2) {
    const matrix = [];

    for (let i = 0; i <= str2.length; i++) {
      matrix[i] = [i];
    }

    for (let j = 0; j <= str1.length; j++) {
      matrix[0][j] = j;
    }

    for (let i = 1; i <= str2.length; i++) {
      for (let j = 1; j <= str1.length; j++) {
        if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
          matrix[i][j] = matrix[i - 1][j - 1];
        } else {
          matrix[i][j] = Math.min(
            matrix[i - 1][j - 1] + 1,
            matrix[i][j - 1] + 1,
            matrix[i - 1][j] + 1
          );
        }
      }
    }

    return matrix[str2.length][str1.length];
  }

  async indexLyrics(lyricsData) {
    try {
      const record = {
        objectID: `${lyricsData.artist}_${lyricsData.title}`.replace(/[^a-zA-Z0-9]/g, '_'),
        artist: lyricsData.artist,
        title: lyricsData.title,
        lyrics: lyricsData.lyrics,
        source: lyricsData.source,
        url: lyricsData.url,
        indexedAt: new Date().toISOString()
      };

      await this.index.saveObject(record);
      console.log(`âś… Indexed lyrics for: ${lyricsData.artist} - ${lyricsData.title}`);

      return true;
    } catch (error) {
      console.error('Algolia indexing error:', error);
      return false;
    }
  }

  async batchIndexLyrics(lyricsArray) {
    try {
      const records = lyricsArray.map((lyrics, index) => ({
        objectID: `${lyrics.artist}_${lyrics.title}`.replace(/[^a-zA-Z0-9]/g, '_'),
        artist: lyrics.artist,
        title: lyrics.title,
        lyrics: lyrics.lyrics,
        source: lyrics.source || 'Batch Import',
        url: lyrics.url,
        indexedAt: new Date().toISOString()
      }));

      await this.index.saveObjects(records);
      console.log(`âś… Batch indexed ${records.length} lyrics`);

      return true;
    } catch (error) {
      console.error('Algolia batch indexing error:', error);
      return false;
    }
  }
}

module.exports = new AlgoliaService();
Enter fullscreen mode Exit fullscreen mode

đź§  What the Code Does

This class, AlgoliaService, wraps around Algolia's SDK to handle three key functions:

Search for lyrics by artist and title

Find the best match based on similarity

Index new lyrics (single or batch)

âś…Key Takeaways

There is a real user need for richer, more complete lyric integration in music apps.

The gap is not due to missing technology — it’s a matter of platform decisions and licensing agreements.

Algolia MCP can act as a powerful bridge to simulate a better search experience, enhancing what’s possible within legal and ethical limits.

AI tools like Gemini can streamline development, making intelligent integration faster and more adaptive.

Top comments (4)

Collapse
 
prema_ananda profile image
Prema Ananda •

Good idea! Even though the project isn't implemented yet, the concept sounds promising.

Would be interesting to see it in action once you build it out.

Collapse
 
daniela_oliveira profile image
Daniela Oliveira •

Thank you!

Collapse
 
ansellmaximilian profile image
Ansell Maximilian •

Awesome concept! I'd recommend deploying it somewhere easily accessible and testable because it would be really cool to see this idea with a nice front end

Thanks for sharing

Some comments may only be visible to logged-in visitors. Sign in to view all comments.