You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
4.0 KiB
JavaScript
134 lines
4.0 KiB
JavaScript
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
const { z } = require("zod");
|
|
const {
|
|
getAccessToken,
|
|
getTopTracks,
|
|
scrapeArtists,
|
|
LLM_PROVIDERS,
|
|
} = require("./get_popularity");
|
|
|
|
const log = (...args) => console.error("[mcp-song-popularity]", ...args);
|
|
|
|
const server = new McpServer({
|
|
name: "song-popularity-tools",
|
|
version: "1.0.0",
|
|
});
|
|
|
|
// Tool 1: get_top_tracks
|
|
server.tool(
|
|
"get_top_tracks",
|
|
"Get the top N most popular tracks for a given artist on Spotify",
|
|
{
|
|
artistName: z.string().describe("The artist or band name to search for"),
|
|
topN: z.number().optional().default(3).describe("Number of top tracks to return (default: 3)"),
|
|
},
|
|
async ({ artistName, topN }) => {
|
|
try {
|
|
log(`get_top_tracks: "${artistName}", topN=${topN}`);
|
|
const token = await getAccessToken();
|
|
const result = await getTopTracks(token, artistName, topN);
|
|
if (!result.artist) {
|
|
return {
|
|
content: [{ type: "text", text: `Artist "${artistName}" not found on Spotify.` }],
|
|
};
|
|
}
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
// Tool 2: scrape_artists_from_url
|
|
server.tool(
|
|
"scrape_artists_from_url",
|
|
"Scrape a web page (concerts, festivals, music venues) and extract artist names using an LLM",
|
|
{
|
|
url: z.string().url().describe("URL of the page to scrape for artist names"),
|
|
provider: z
|
|
.enum(Object.keys(LLM_PROVIDERS))
|
|
.optional()
|
|
.default("anthropic")
|
|
.describe("LLM provider to use for extraction (default: anthropic)"),
|
|
},
|
|
async ({ url, provider }) => {
|
|
try {
|
|
log(`scrape_artists_from_url: "${url}", provider=${provider}`);
|
|
const artists = await scrapeArtists(url, provider, log);
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(artists, null, 2) }],
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
// Tool 3: get_top_songs_from_url
|
|
server.tool(
|
|
"get_top_songs_from_url",
|
|
"Scrape a web page to find artists, then return the top N songs for each artist from Spotify",
|
|
{
|
|
url: z.string().url().describe("URL of the page to scrape for artist names"),
|
|
topN: z.number().optional().default(3).describe("Number of top songs per artist (default: 3)"),
|
|
provider: z
|
|
.enum(Object.keys(LLM_PROVIDERS))
|
|
.optional()
|
|
.default("anthropic")
|
|
.describe("LLM provider to use for extraction (default: anthropic)"),
|
|
},
|
|
async ({ url, topN, provider }) => {
|
|
try {
|
|
log(`get_top_songs_from_url: "${url}", topN=${topN}, provider=${provider}`);
|
|
const artistNames = await scrapeArtists(url, provider, log);
|
|
if (artistNames.length === 0) {
|
|
return {
|
|
content: [{ type: "text", text: "No artist names found on the page." }],
|
|
};
|
|
}
|
|
|
|
log(`Found ${artistNames.length} artist(s), looking up tracks...`);
|
|
const token = await getAccessToken();
|
|
const results = {};
|
|
|
|
for (const name of artistNames) {
|
|
const result = await getTopTracks(token, name, topN);
|
|
if (result.artist) {
|
|
results[result.artist] = result.tracks;
|
|
} else {
|
|
log(`Artist "${name}" not found on Spotify, skipping.`);
|
|
}
|
|
}
|
|
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
async function main() {
|
|
const transport = new StdioServerTransport();
|
|
await server.connect(transport);
|
|
log("Spotify MCP server started");
|
|
}
|
|
|
|
main().catch((err) => {
|
|
log("Fatal error:", err);
|
|
process.exit(1);
|
|
});
|