mcp-lyricscom
This server lets you search and read song lyrics from lyrics.com — no API key or account required.
Search by lyric fragment (
search_lyrics): Find songs containing a specific word or phrase, with a matching excerpt shown per result. Supports pagination, verification modes (snippet,full,none), and local word-boundary-aware filtering so results genuinely contain the searched term.Search by song title (
search_songs): Find songs by title, optionally filtered by artist, with strict or loose matching and pagination.Fetch full lyrics (
get_lyrics): Retrieve complete lyrics using a song's ID or URL. Supports paginated reading viaoffset/next_offsetandmax_chars, plus optional word highlighting.Transparent error handling: Distinguishes "no lyrics on file" from real issues like throttling (
throttled), blocked User-Agent, or parse failures — never misreports errors as empty results.Attribution: Every result includes artist, title, and source URL.
Caching: In-memory cache (default 15 min, 200 entries) to avoid repeated hits.
Tunable: Environment variables for rate limiting, timeouts, retries, caching, and User-Agent.
Quick start: Run via
npx -y mcp-lyricscomor one-click install into Cursor/VS Code.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-lyricscomfind songs whose lyrics include 'ground control to major tom'"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-lyricscom
lyrics.com is a large public catalogue of song lyrics. It files a song under its title, its artist, the album it appeared on and the year, and it holds the words themselves. Its search reaches inside those words.
This server connects a chat client to that catalogue. You can search for a song by a line you remember, search by title and artist, and read the words of one song, a slice at a time, with the words you were looking for located in the text. It needs no API key and no account.
Install
One-click install
Claude Code
claude mcp add lyricscom -- npx -y mcp-lyricscomClaude Desktop, Cursor, and any client using the standard config format
{
"mcpServers": {
"lyricscom": {
"command": "npx",
"args": ["-y", "mcp-lyricscom"]
}
}
}Node 24 or later is required, and no environment variable has to be set.
With Docker
{
"mcpServers": {
"lyricscom": {
"command": "docker",
"args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-lyricscom:2.0.1"]
}
}
}-i keeps stdin open, which is where the protocol travels, and -t is left out
because a TTY rewrites the stream. The container needs outbound HTTPS to
www.lyrics.com, and nothing else: no volume, no port, no credential.
Bundle, without npm
Download mcp-lyricscom-2.0.1.mcpb from
the latest release
and open it. A client that supports MCP bundles installs it on its own, with no
npm and no configuration file to edit. The bundle carries its dependencies, so
nothing is fetched at install time.
Related MCP server: LyricsGenius MCP Server
What you can ask
"Which song goes 'I've got a hand for you'?"
"Find me the lyrics of Wichita Lineman by Glen Campbell."
"Read me the second half of those words."
"Where does the word 'lineman' appear in that song?"
"What albums is that song on?"
The ordinary path runs from a search to a reading: a row carries an id, and
get_lyrics takes that id.
Tools
Tool | What it does |
| Finds a song from a line inside its words. |
| Finds songs by title, narrowed by artist. |
| Reads the words of one song, a slice at a time. |
search_lyrics
Finds a song from words inside its lyrics. The site ranks loosely, so a match is checked before it is served.
Argument | Type | Required | What it does |
| string, 1 to 120 characters | yes | The line, or part of it, to look for. |
| integer, 1 to 50, default | no | Rows to serve. |
| integer, 1 to 20, default | no | Which page of rows. |
|
| no | How to confirm the words really appear. |
| boolean, default | no | Carry the matching line with each row. |
verify decides what a row is worth. snippet checks the excerpt the site
already returned and costs nothing. full fetches up to five song pages and
checks the complete words, which is slow and can bring on rate limiting. none
serves what the site ranked, unchecked.
In return: rows carrying id, which get_lyrics takes; title; artist;
album and year, null where the catalogue states none; source_url; and
excerpt, the matching line. raw_result_count is what the site returned and
filtered_out how many rows the check removed, so the two together say how loose
the ranking was. has_more and next_page continue.
search_songs
Finds songs by title, narrowed by artist.
Argument | Type | Required | What it does |
| string, 1 to 120 characters | yes | The song title, or part of it. |
| string, up to 120 characters | no | Keep the songs credited to this artist. |
| integer, 1 to 50, default | no | Rows to serve. |
| integer, 1 to 20, default | no | Which page of rows. |
|
| no | How closely the artist has to match. |
In return: the rows search_lyrics returns, with artist_filter echoing
what was asked for and filtered_out counting what the artist restriction
removed. strict keeps the artists whose name matches as written; loose
accepts a name written differently.
get_lyrics
Reads the words of one song. Long lyrics are served a slice at a time.
Argument | Type | Required | What it does |
| string | one of two | The song id a search row carries. |
| a lyrics.com URL | one of two | The address of the song page. |
| integer, 200 to 20000, default | no | Characters of text to serve in this call. |
| integer, 0 or more, default | no | Character offset to resume from. |
| string, up to 120 characters | no | Words to locate inside the text. |
In return: status, reading ok or no_lyrics for a page the site holds
without words; title, artist and source_url; and lyrics, the slice
itself. The reading is described by total_chars, returned_chars, offset,
next_offset and truncated: pass next_offset back to read on, and null
there means the end. line_count counts the lines of the slice, and highlight
answers for each word whether it was found and on which line_number, which is
null when it was not.
Configuration
Every variable is optional. Set them in the env block of your client config.
Variable | Default | What it does |
| the project identity | Names your application to the site, with an address where a person can be reached. |
|
| Gap between two requests, from 500 to 60000. |
|
| Deadline for one request, from 1000 to 120000. |
|
| Attempts after a transient failure, from 0 to 10. |
|
| How long an answer stays in memory, from 0 to 86400000. |
|
| Answers held in memory at once, from 0 to 10000. |
|
|
|
A value outside its range falls back to the default, and the reason is written to stderr.
On the User-Agent. This server names the project and links to its repository,
and the site serves that. It does refuse some generic tool agents outright: a
plain curl gets a 403. A blocked_user_agent error means the identity was
refused, and LYRICSCOM_USER_AGENT lets you set one of your choosing. What you
put there is your call and your responsibility.
Errors
Every failure carries one of these codes, a message, and where it helps a hint naming the next move.
Code | What happened | What to do |
| The site answered, and holds no such song. | Check the id with |
| The arguments were refused before any request went out. | Read the message, which names the argument. |
| The site asked this client to slow down. | Wait, then call again with the same arguments. The song is still there. |
| The site refused the identity this client sent. | Set |
| The page loaded and the expected content was absent. | Report it at the issue tracker. |
| The request did not complete. | Try again shortly. |
| The request passed its deadline. | Raise |
throttled and blocked_user_agent are this server's two names for a refusal to
serve, and a caller reading several sources normalises them onto whatever it
calls rate limiting.
As a library
The layer reading the site is published on its own, with its pacing, its cache and its errors, and with no protocol attached.
import { LyricsComClient } from "mcp-lyricscom/client";
const client = new LyricsComClient();
const { data, cached } = await client.getSong({ id: "1234567" });
console.log(data.title, data.artist, cached);search and getSong each answer { data, cached }, and throw an error
carrying one of the codes above. The floor between two requests holds here as
well.
Pacing and attribution
Requests go out one at a time with at least a second between them, and the floor
of half a second holds however the server is configured. A verify: "full"
search fetches up to five song pages, which is the most expensive thing this
server does.
Every result carries the artist, the title and the address of the song page. Song lyrics are the work of their authors and publishers. This project claims no rights over them, ships no database of them, and writes nothing to disk.
This MCP server is an unofficial project, with no affiliation to lyrics.com.
Privacy
This server collects nothing about you and sends nothing to its author. It runs
on your machine, contacts www.lyrics.com and nothing else, holds its answers in memory
while it runs, and writes nothing to disk.
PRIVACY.md states what a request carries and which settings change
any of it.
Development
npm install
npm run build:fixtures
npm test
npm run checkTests run against generated fixtures and make no network request. The live suite,
npm run test:live, makes one request per route and runs nightly against the
site itself.
Contributing
Bugs, questions and ideas belong in the issue tracker. Pull requests are welcome; opening an issue first helps agree on the shape of the change. See CONTRIBUTING.md.
License
MIT, see LICENSE. The lyrics belong to their authors and publishers.
mcp-lyricscom (français)
lyrics.com est un grand catalogue public de paroles de chansons. Il classe une chanson sous son titre, son artiste, l'album où elle a paru et l'année, et il contient les paroles elles-mêmes. Sa recherche va à l'intérieur de ces paroles.
Ce serveur relie un client de conversation à ce catalogue. On peut y chercher une chanson par un vers dont on se souvient, chercher par titre et par artiste, et lire les paroles d'une chanson par tranches, avec les mots cherchés localisés dans le texte. Aucune clé d'API, aucun compte.
Installation
Installation en un clic
Claude Code
claude mcp add lyricscom -- npx -y mcp-lyricscomClaude Desktop, Cursor, et tout client au format de configuration standard
{
"mcpServers": {
"lyricscom": {
"command": "npx",
"args": ["-y", "mcp-lyricscom"]
}
}
}Node 24 ou plus récent est nécessaire, et aucune variable d'environnement n'est à renseigner.
Avec Docker
{
"mcpServers": {
"lyricscom": {
"command": "docker",
"args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-lyricscom:2.0.1"]
}
}
}-i garde l'entrée standard ouverte, qui est le canal du protocole, et -t est
omis parce qu'un TTY réécrit le flux. Le conteneur a besoin d'un accès HTTPS
sortant vers www.lyrics.com, et de rien d'autre : aucun volume, aucun port,
aucun identifiant.
Bundle, sans npm
Téléchargez mcp-lyricscom-2.0.1.mcpb depuis
la dernière publication
et ouvrez-le. Un client qui gère les bundles MCP l'installe seul, sans npm et
sans fichier de configuration à modifier. Le bundle emporte ses dépendances, donc
rien n'est téléchargé à l'installation.
Ce qu'on peut demander
« Quelle est la chanson qui dit "I've got a hand for you" ? »
« Trouve-moi les paroles de Wichita Lineman par Glen Campbell. »
« Lis-moi la seconde moitié de ces paroles. »
« Où apparaît le mot "lineman" dans cette chanson ? »
« Sur quels albums cette chanson figure-t-elle ? »
Le chemin ordinaire va d'une recherche à une lecture : une ligne porte un id,
et get_lyrics reprend cet identifiant.
Les outils
Outil | Ce qu'il fait |
| Trouve une chanson à partir d'un vers de ses paroles. |
| Trouve des chansons par titre, resserrées par artiste. |
| Lit les paroles d'une chanson, par tranches. |
search_lyrics
Trouve une chanson à partir de mots contenus dans ses paroles. Le site classe largement, donc une correspondance est vérifiée avant d'être servie.
Argument | Type | Requis | Ce qu'il fait |
| chaîne, 1 à 120 caractères | oui | Le vers, ou une partie, à chercher. |
| entier, 1 à 50, défaut | non | Lignes à servir. |
| entier, 1 à 20, défaut | non | Quelle page de lignes. |
|
| non | Comment confirmer que les mots y figurent. |
| booléen, défaut | non | Porter le vers correspondant sur chaque ligne. |
verify décide de ce que vaut une ligne. snippet vérifie l'extrait que le site
a déjà rendu et ne coûte rien. full va chercher jusqu'à cinq pages de chansons
et vérifie les paroles entières, ce qui est lent et peut déclencher une
limitation. none sert ce que le site a classé, sans vérification.
En retour : des lignes portant id, que get_lyrics reprend ; title ;
artist ; album et year, null là où le catalogue n'indique rien ;
source_url ; et excerpt, le vers correspondant. raw_result_count est ce que
le site a rendu et filtered_out le nombre de lignes que la vérification a
retirées, si bien que les deux ensemble disent à quel point le classement était
large. has_more et next_page poursuivent.
search_songs
Trouve des chansons par titre, resserrées par artiste.
Argument | Type | Requis | Ce qu'il fait |
| chaîne, 1 à 120 caractères | oui | Le titre de la chanson, ou une partie. |
| chaîne, jusqu'à 120 caractères | non | Ne garder que les chansons de cet artiste. |
| entier, 1 à 50, défaut | non | Lignes à servir. |
| entier, 1 à 20, défaut | non | Quelle page de lignes. |
|
| non | La rigueur de la correspondance sur l'artiste. |
En retour : les lignes que rend search_lyrics, avec artist_filter qui
redonne ce qui a été demandé et filtered_out qui compte ce que la restriction
sur l'artiste a retiré. strict garde les artistes dont le nom correspond tel
qu'écrit ; loose accepte un nom écrit autrement.
get_lyrics
Lit les paroles d'une chanson. Des paroles longues sont servies par tranches.
Argument | Type | Requis | Ce qu'il fait |
| chaîne | l'un des deux | L'identifiant que porte une ligne. |
| une adresse lyrics.com | l'un des deux | L'adresse de la page de la chanson. |
| entier, 200 à 20000, défaut | non | Caractères de texte à servir dans cet appel. |
| entier, 0 ou plus, défaut | non | Position en caractères où reprendre. |
| chaîne, jusqu'à 120 caractères | non | Des mots à localiser dans le texte. |
En retour : status, valant ok ou no_lyrics pour une page que le site
contient sans paroles ; title, artist et source_url ; et lyrics, la
tranche elle-même. La lecture est décrite par total_chars, returned_chars,
offset, next_offset et truncated : redonnez next_offset pour poursuivre,
et null marque la fin. line_count compte les lignes de la tranche, et
highlight répond pour chaque mot s'il a été found et à quel line_number,
null quand il ne l'a pas été.
Configuration
Chaque variable est facultative. Elles se posent dans le bloc env de la
configuration du client.
Variable | Défaut | Ce qu'elle fait |
| l'identité du projet | Nomme votre application auprès du site, avec une adresse où joindre une personne. |
|
| Écart entre deux requêtes, de 500 à 60000. |
|
| Délai d'une requête, de 1000 à 120000. |
|
| Tentatives après un échec passager, de 0 à 10. |
|
| Durée pendant laquelle une réponse reste en mémoire, de 0 à 86400000. |
|
| Réponses gardées en mémoire à la fois, de 0 à 10000. |
|
|
|
Une valeur hors de sa plage retombe sur le défaut, et la raison est écrite sur la sortie d'erreur.
À propos du User-Agent. Ce serveur nomme le projet et renvoie vers son dépôt,
et le site le sert. Il refuse en revanche certains agents d'outils génériques :
un curl nu reçoit un 403. Une erreur blocked_user_agent signifie que
l'identité envoyée a été refusée, et LYRICSCOM_USER_AGENT permet d'en poser une
de votre choix. Ce que vous y mettez relève de votre décision et de votre
responsabilité.
Erreurs
Chaque échec porte un de ces codes, un message, et quand cela aide une indication du geste suivant.
Code | Ce qui s'est passé | Que faire |
| Le site a répondu, et n'a pas cette chanson. | Vérifiez l'identifiant avec |
| Les arguments ont été refusés avant toute requête. | Lisez le message, qui nomme l'argument. |
| Le site demande à ce client de ralentir. | Attendez, puis rappelez avec les mêmes arguments. La chanson est toujours là. |
| Le site a refusé l'identité envoyée par ce client. | Posez |
| La page a chargé et le contenu attendu est absent. | Signalez-le sur le suivi d'incidents. |
| La requête n'a pas abouti. | Réessayez sous peu. |
| La requête a dépassé son délai. | Augmentez |
throttled et blocked_user_agent sont les deux noms que ce serveur donne à un
refus de servir, et un appelant qui lit plusieurs sources les ramène sur ce qu'il
appelle une limitation de débit.
Comme bibliothèque
La couche qui lit le site est publiée seule, avec son rythme, son cache et ses erreurs, sans protocole attaché.
import { LyricsComClient } from "mcp-lyricscom/client";
const client = new LyricsComClient();
const { data, cached } = await client.getSong({ id: "1234567" });
console.log(data.title, data.artist, cached);search et getSong répondent chacun { data, cached }, et lèvent une erreur
portant un des codes ci-dessus. Le plancher entre deux requêtes tient également
ici.
Rythme et attribution
Les requêtes partent une à une avec au moins une seconde entre elles, et le
plancher d'une demi-seconde tient quelle que soit la configuration. Une recherche
en verify: "full" va chercher jusqu'à cinq pages de chansons, ce qui est la
chose la plus coûteuse que fait ce serveur.
Chaque résultat porte l'artiste, le titre et l'adresse de la page de la chanson. Les paroles sont l'œuvre de leurs auteurs et de leurs éditeurs. Ce projet ne revendique aucun droit dessus, n'embarque aucune base de paroles et n'écrit rien sur le disque.
Ce MCP est un projet non officiel, sans affiliation à lyrics.com.
Confidentialité
Ce serveur ne collecte rien sur vous et n'envoie rien à son auteur. Il tourne sur
votre machine, ne joint que www.lyrics.com, garde ses réponses en mémoire le temps qu'il
tourne, et n'écrit rien sur le disque. PRIVACY.md dit ce qu'une
requête emporte et quels réglages changent cela.
Développement
npm install
npm run build:fixtures
npm test
npm run checkLes tests s'exécutent sur des fixtures engendrées et n'émettent aucune requête.
La suite en direct, npm run test:live, émet une requête par route et tourne
chaque nuit contre le site lui-même.
Contribuer
Les anomalies, les questions et les idées ont leur place dans le suivi d'incidents. Les propositions de modification sont bienvenues ; ouvrir un ticket d'abord aide à s'accorder sur la forme du changement. Voir CONTRIBUTING.md.
Licence
MIT, voir LICENSE. Les paroles appartiennent à leurs auteurs et à leurs éditeurs.
Available Tools
3 toolsget_lyricsGet full lyricsARead-onlyIdempotent
Fetch the full lyrics of one song from lyrics.com, given the song id or URL returned by search_lyrics or search_songs. Lyrics can be long, so the response is truncated by default: check 'truncated' and call again with 'offset' set to 'next_offset' to continue reading. Some lyrics.com pages legitimately have no lyrics on file; those come back with status "no_lyrics" and an empty lyrics field, which is a valid answer and not an error, so do not retry them. Always cite 'source_url' and the artist when showing lyrics to a user.
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | lyrics.com numeric song id, as returned by the search tools. Preferred over 'url'. | |
| url | No | Full lyrics.com song URL. Only www.lyrics.com URLs are accepted. Ignored when 'id' is given. | |
| offset | No | Character offset to resume from, for lyrics longer than max_chars. | |
| highlight | No | Optional word. When set, the response reports whether it appears in the lyrics and on which line. | |
| max_chars | No | Maximum characters of lyrics text to return in this call. |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | Yes | |
| notes | Yes | |
| title | Yes | |
| artist | Yes | |
| lyrics | Yes | Slice of the lyrics text. Empty when status is 'no_lyrics'. |
| offset | Yes | |
| source | Yes | |
| status | Yes | |
| highlight | Yes | |
| truncated | Yes | |
| line_count | Yes | |
| source_url | Yes | |
| attribution | Yes | Ready-to-display credit line. |
| next_offset | Yes | |
| total_chars | Yes | |
| returned_chars | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already establish read-only, non-destructive, and idempotent behavior. The description adds valuable behavioral details beyond those: truncation with a 'truncated' flag, continuation via 'next_offset', and the legitimate 'no_lyrics' status with empty lyrics. It also specifies attribution requirements, going well beyond what annotations convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is four focused sentences, each contributing essential information: the core action, pagination behavior, a critical edge case, and a user-facing citation requirement. It is front-loaded with the main purpose and avoids filler, making it easy for an agent to parse quickly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only retrieval tool with an output schema and no required parameters, the description covers everything needed: how to specify the target, how to handle long lyrics, how to interpret a legitimately empty result, and what to show users. The presence of an output schema means return-value details like 'source_url' and 'truncated' do not need to be repeated here.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so each parameter is already documented. The description adds practical semantics by explaining that id/url come from the search tools, that id is the preferred option, and that 'offset' should be set to the previously returned 'next_offset' for continued reading. This gives the agent more operational context than the schema alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Fetch') with a concrete resource ('full lyrics of one song from lyrics.com') and explicitly ties the required input to the sibling search tools. This clearly distinguishes get_lyrics from search_songs and search_lyrics, which find songs or lyrics pages rather than retrieving a specific song's lyrics.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states that the tool should be called with an id or URL returned by search_lyrics or search_songs, giving a concrete precondition. It also provides an exclusion by explaining that a 'no_lyrics' response is a valid outcome and should not be retried, which is actionable and prevents unnecessary repeated calls.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_lyricsSearch lyrics by wordARead-onlyIdempotent
Search lyrics.com for songs whose lyrics contain a given word or phrase. Returns matching songs with artist, title, a short excerpt of the line where the word appears, and the lyrics.com id and URL needed to fetch the full text with get_lyrics. Use this when someone remembers a fragment of lyrics but not the song, or wants songs mentioning a word or theme. One call returns one page of lyrics.com results; to see more, increase 'page' rather than 'limit'. lyrics.com also returns loose and title-only matches, so results are filtered locally to keep only songs where the word genuinely appears in the lyrics. Set 'verify' to "none" to see the raw, unfiltered list.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | Result page on lyrics.com. Each page holds up to 24 raw results. | |
| limit | No | Maximum songs to return from this page. | |
| query | Yes | Word or short phrase to look for inside the lyrics, for example 'autrefois'. | |
| verify | No | How to confirm the query really appears in the lyrics. 'snippet' checks the excerpt lyrics.com already returned and costs nothing. 'full' fetches up to 5 song pages and checks the complete lyrics, which is slow and can trigger rate limiting. 'none' disables filtering. | snippet |
| include_excerpt | No | Include the matching lyric line excerpt in each result. |
Output Schema
| Name | Required | Description |
|---|---|---|
| page | Yes | |
| notes | Yes | Caveats worth knowing, such as a cache hit or partial verification. |
| query | Yes | |
| source | Yes | |
| results | Yes | |
| has_more | Yes | |
| next_page | Yes | |
| filtered_out | Yes | Rows dropped because the query was not found in their lyrics. |
| raw_result_count | Yes | Rows lyrics.com returned before local filtering and deduplication. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, openWorldHint, idempotentHint, and destructiveHint=false, and the description adds substantial behavioral context beyond that: one call returns one page, increasing page is preferred over limit, lyrics.com returns loose and title-only matches so results are filtered locally, and verify='full' can be slow and trigger rate limiting. None of this contradicts the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Six sentences, no filler, and every sentence earns its place: purpose, return value, use case, pagination behavior, filtering rationale, and verification option. The most important scoping information is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only search tool, this is complete: it covers trigger scenarios, return shape, pagination, filtering behavior, verification tradeoffs, and the connection to get_lyrics. The output schema handles return details, and annotations handle the safety profile, so nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so every parameter is already documented, but the description adds operational guidance beyond the schema: use 'page' rather than 'limit' to see more results, verify='none' exposes the raw unfiltered list, and verify='full' fetches up to 5 pages with rate-limit risk. This materially helps an agent choose parameter values correctly.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Search lyrics.com for songs whose lyrics contain a given word or phrase.' It also distinguishes itself from get_lyrics by stating it returns the id and URL needed to call get_lyrics, and the use case ('remembers a fragment of lyrics but not the song') separates it from a song-title search like search_songs.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives an explicit trigger: 'Use this when someone remembers a fragment of lyrics but not the song, or wants songs mentioning a word or theme.' It does not explicitly name search_songs as the alternative or state when not to use this tool, so it stops short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_songsSearch songs by titleARead-onlyIdempotent
Find songs on lyrics.com by their title. Returns candidate songs with artist, album, year, and the lyrics.com id and URL. Use this when someone names a song and you need its id to call get_lyrics, or to tell covers and versions by different artists apart. An optional 'artist' narrows the list. lyrics.com searches titles and lyrics together, so results are ranked and filtered locally by title match; use match="strict" to keep only genuine title matches.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | ||
| limit | No | ||
| match | No | 'strict' keeps only results whose title matches the query as a whole title, ignoring suffixes such as '(Remastered)'. 'loose' keeps anything lyrics.com returned, best match first. | loose |
| title | Yes | Song title, or part of it. | |
| artist | No | Optional artist name, matched case-insensitively against each result. |
Output Schema
| Name | Required | Description |
|---|---|---|
| page | Yes | |
| notes | Yes | |
| title | Yes | |
| source | Yes | |
| results | Yes | |
| has_more | Yes | |
| next_page | Yes | |
| filtered_out | Yes | |
| artist_filter | Yes | |
| raw_result_count | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, and non-destructive behavior, and the description adds valuable non-obvious behavior: lyrics.com searches titles and lyrics together, so results are ranked and filtered locally by title match, with 'strict' needed to keep genuine title matches. This explains potential unexpected results beyond what the annotations convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tightly written sentences cover purpose, return shape, use cases, and the matching caveat without wasted words. Key information is front-loaded in the first sentence, and every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description is complete for a read-only search tool: it states what it returns, when to use it, how to constrain results, and why results may be noisy. With an output schema present and rich annotations, no essential calling information is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already describes title, artist, and match, and the description reinforces artist narrowing and strict matching. However, it adds little about page and limit, and schema description coverage is only 60%, so the description doesn't fully compensate for the undeclared pagination parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource: 'Find songs on lyrics.com by their title,' and explicitly lists the return fields (artist, album, year, id, URL), making the tool's function unmistakable. This also distinguishes it from the sibling get_lyrics and search_lyrics, since it returns song candidates rather than lyric content.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear usage triggers: use this when a user names a song and you need its id for get_lyrics, or when disambiguating covers and versions by different artists. It also explains the optional artist narrowing and the strict match mode. It does not explicitly mention when to prefer search_lyrics, so it stops short of complete alternative guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v2.0.0- Changed
get_lyrics15 fields changed- added
Input schema / properties / offset / maximumAdded value: +9007199254740991 - added
Output schema / properties / artist / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - removed
Output schema / properties / artist / typeRemoved value: -[ - "string", - "null" -] - changed
Output schema / properties / highlight / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "found": { - "type": "boolean" - }, - "line": { - "type": [ - "string", - "null" - ] - }, - "line_number": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ] - }, - "word": { - "type": "string" - } - }, - "required": [ - "word", - "found", - "line_number", - "line" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "found": { + "type": "boolean" + }, + "line": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "line_number": { + "anyOf": [ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "word": { + "type": "string" + } + }, + "required": [ + "word", + "found", + "line_number", + "line" + ], + "type": "object" + }, + { + "type": "null" + } +] - added
Output schema / properties / line_count / maximumAdded value: +9007199254740991 - added
Output schema / properties / line_count / minimumAdded value: +-9007199254740991 - changed
Output schema / properties / next_offset / anyOfPrevious value: -[ - { - "type": "integer" - }, - { - "type": "null" - } -]New value: +[ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } +] - added
Output schema / properties / offset / maximumAdded value: +9007199254740991 - added
Output schema / properties / offset / minimumAdded value: +-9007199254740991 - added
Output schema / properties / returned_chars / maximumAdded value: +9007199254740991 - added
Output schema / properties / returned_chars / minimumAdded value: +-9007199254740991 - added
Output schema / properties / title / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - removed
Output schema / properties / title / typeRemoved value: -[ - "string", - "null" -] - added
Output schema / properties / total_chars / maximumAdded value: +9007199254740991 - added
Output schema / properties / total_chars / minimumAdded value: +-9007199254740991
- Changed
search_lyrics12 fields changed- added
Output schema / properties / filtered_out / maximumAdded value: +9007199254740991 - added
Output schema / properties / filtered_out / minimumAdded value: +-9007199254740991 - changed
Output schema / properties / next_page / anyOfPrevious value: -[ - { - "type": "integer" - }, - { - "type": "null" - } -]New value: +[ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } +] - added
Output schema / properties / page / maximumAdded value: +9007199254740991 - added
Output schema / properties / page / minimumAdded value: +-9007199254740991 - added
Output schema / properties / raw_result_count / maximumAdded value: +9007199254740991 - added
Output schema / properties / raw_result_count / minimumAdded value: +-9007199254740991 - added
Output schema / properties / results / items / properties / album / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - removed
Output schema / properties / results / items / properties / album / typeRemoved value: -[ - "string", - "null" -] - added
Output schema / properties / results / items / properties / excerpt / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - removed
Output schema / properties / results / items / properties / excerpt / typeRemoved value: -[ - "string", - "null" -] - changed
Output schema / properties / results / items / properties / year / anyOfPrevious value: -[ - { - "type": "integer" - }, - { - "type": "null" - } -]New value: +[ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } +]
- Changed
search_songs14 fields changed- added
Output schema / properties / artist_filter / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - removed
Output schema / properties / artist_filter / typeRemoved value: -[ - "string", - "null" -] - added
Output schema / properties / filtered_out / maximumAdded value: +9007199254740991 - added
Output schema / properties / filtered_out / minimumAdded value: +-9007199254740991 - changed
Output schema / properties / next_page / anyOfPrevious value: -[ - { - "type": "integer" - }, - { - "type": "null" - } -]New value: +[ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } +] - added
Output schema / properties / page / maximumAdded value: +9007199254740991 - added
Output schema / properties / page / minimumAdded value: +-9007199254740991 - added
Output schema / properties / raw_result_count / maximumAdded value: +9007199254740991 - added
Output schema / properties / raw_result_count / minimumAdded value: +-9007199254740991 - added
Output schema / properties / results / items / properties / album / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - removed
Output schema / properties / results / items / properties / album / typeRemoved value: -[ - "string", - "null" -] - added
Output schema / properties / results / items / properties / excerpt / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - removed
Output schema / properties / results / items / properties / excerpt / typeRemoved value: -[ - "string", - "null" -] - changed
Output schema / properties / results / items / properties / year / anyOfPrevious value: -[ - { - "type": "integer" - }, - { - "type": "null" - } -]New value: +[ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } +]
1 tool update
v1.4.0- Changed
get_lyrics2 fields changed- added
Output schema / properties / notesAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "status", - "id", - "title", - "artist", - "source_url", - "lyrics", - "total_chars", - "returned_chars", - "offset", - "next_offset", - "truncated", - "line_count", - "highlight", - "attribution", - "source" -]New value: +[ + "status", + "id", + "title", + "artist", + "source_url", + "lyrics", + "total_chars", + "returned_chars", + "offset", + "next_offset", + "truncated", + "line_count", + "highlight", + "attribution", + "source", + "notes" +]
3 tool updates
v1.0.0- First observed
get_lyrics - First observed
search_lyrics - First observed
search_songs
TDQS
Scored across 3 tools
Each tool has a clearly distinct purpose: search_songs finds by title, search_lyrics finds by lyric fragment, and get_lyrics retrieves the full text for a known song id. The descriptions explicitly state when to use each and how they chain together, so there is no real ambiguity.
All tool names follow a consistent verb_noun pattern with snake_case: search_songs, search_lyrics, and get_lyrics. The naming convention is uniform and immediately understandable.
Three tools is an appropriate size for a focused lyrics lookup service. Each tool performs an essential step in the workflow: search by title, search by lyric content, and fetch full lyrics, with no redundant tools.
The tool set covers the full user-visible lifecycle for a lyrics service: finding a song by title, finding a song from a remembered lyric fragment, and retrieving the full lyrics. Pagination and no_lyrics cases are explicitly handled, leaving no obvious dead ends in the core workflow.
Maintenance
Related MCP Connectors
MCP server for Suno AI music generation, lyrics, and covers
An MCP server that provides tools to discover and retrieve podcast episodes transcripts.
MCP server for RiverScript, an AI transcription platform - fetches transcripts shared via a link.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceAn MCP server for Spotify control and synchronized lyrics retrieval that enables playback management, queue navigation, and music search capabilities. It also features perception tools for real-time track analysis, including BPM, key detection, and timestamped lyrics.1163Apache 2.0
- AlicenseNot gradedqualityDmaintenanceAn MCP server that enables searching Genius.com for artists, songs, and albums, retrieving song lyrics, and exploring artist discographies through natural language.3MIT
- AlicenseNot gradedqualityDmaintenanceMCP server that enables AI assistants to search and retrieve music data (songs, lyrics, playlists, albums, artists) from Netease Cloud Music.MIT
- FlicenseAqualityDmaintenanceMCP server for Suno music generation API that enables generating lyrics and custom songs with style tags, model selection, and automatic polling for task completion.2-