internet-radio-mcp is a small open-source MCP server. It lets Claude, Cursor, VS Code Copilot and other MCP clients search the Radio Browser directory, about 50,000 stations with working streams, and get each station's direct stream URL. It has five read-only tools, needs no API key, and runs with one command:
npx -y internet-radio-mcp The interesting part wasn't the radio. It was realising that every field the server returns goes straight into a language model's context, and that anyone in the world can edit those fields.
The threat is the station name
Radio Browser is community-maintained: anyone can add or edit a station. So a station could be named "Ignore previous instructions and ...". When an assistant asks for jazz stations in Brazil, that name arrives in the tool result next to the real ones. That's indirect prompt injection, and an MCP server is exactly the kind of component that carries it.
You can't make it impossible. You can make it small, visible and boring.
What the server does
One line per field, with nothing hidden. Line breaks and control characters become spaces. Zero-width characters, bidirectional controls, private-use code points, the Unicode "tag" block and other invisible default-ignorable code points are removed. That matters more for an LLM than for a person: those characters can carry text a human reviewer never sees. The exceptions are the emoji selector and the zero-width joiners that real scripts need, and even those survive only in runs of two.
Length caps. Names are cut at 120 characters, tags at 40, other fields at 60. A long "name" is the easiest place to hide a paragraph of instructions.
Only real URLs. stream_url, homepage and favicon must be plain http or https, written out in full, with no credentials or backslashes. javascript:, data:, file:, https:host and bare words are dropped, and URLs are returned in their parsed, canonical form. A station without a valid id or a playable stream is left out entirely.
Say it out loud. The server instructions and every result state that these fields are data from a public directory, not instructions. That doesn't stop a determined model from following text, but it gives the client's model the right frame.
Read-only by design. Every tool is annotated readOnlyHint: true and openWorldHint: true. There's nothing to write, delete or spend, so the worst a poisoned name can do is mislead, not act.
Results that are easy to check
Each tool result carries three things: a one-line human summary, the same data as JSON text, and structuredContent that matches the tool's outputSchema. Clients that understand structured output get typed data; the rest still get readable text. Inputs are bounded too: limit is 1 to 50 for searches, country codes are ISO 3166-1 alpha-2, and order is an enum.
Fail loudly, keep running
The server tries three Radio Browser mirrors in order, each with an 8-second timeout. Replies are capped at 5 MB and must be a JSON list; a mirror that redirects or returns something else counts as failed. If all three fail, the tool returns a clear isError: true result instead of crashing the server or inventing an answer.
Try it
In Claude Code:
claude mcp add internet-radio -- npx -y internet-radio-mcp Then ask something like "find jazz stations in Brazil" or "what's trending on internet radio right now?". The README has configs for Claude Desktop, Cursor, VS Code, Gemini CLI and Docker, and the server is in the official MCP Registry as io.github.AlonDrilich/internet-radio-mcp.
It's MIT-licensed, plain ES modules with JSDoc types, and depends only on the official MCP TypeScript SDK and zod. If you build an MCP server over any public, editable data set, the same list applies: one line per field, nothing invisible, capped lengths, canonical URLs, read-only tools, and a clear statement that the data is data.
