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

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);
});