fundownloader-link-mcp
# fundownloader-link-mcp
A local, **read-only** [Model Context Protocol](https://modelcontextprotocol.io) server that validates and canonicalizes Spotify track/playlist, Instagram post/reel, and Twitter/X status URLs.
It parses links. It does not fetch metadata, check whether content exists, or download media.
[](https://github.com/boddapQ/fundownloader-link-mcp/actions/workflows/ci.yml)
## Why this exists
Agents paste media links constantly, and the same track arrives in a dozen forms:
```
https://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC
https://open.spotify.com/intl-de/track/4uLU6hMCjMI75M1A2tKUQC?si=abc123
https://open.spotify.com/embed/track/4uLU6hMCjMI75M1A2tKUQC
spotify:track:4uLU6hMCjMI75M1A2tKUQC
```
Without normalization, an agent stores the same track five times, or retypes a URL and gets it wrong. This server turns any supported form into one canonical URL, deterministically, before the model reasons about it.
## Requirements
- Node.js **22.12+**
- Any MCP client (Claude Desktop, Cursor, VS Code, Cline, Windsurf, etc.)
## Install
From source:
```bash
git clone https://github.com/boddapQ/fundownloader-link-mcp.git
cd fundownloader-link-mcp
npm install --ignore-scripts
```
Then add it to your MCP client configuration:
```json
{
"mcpServers": {
"fundownloader-links": {
"command": "node",
"args": ["/absolute/path/to/fundownloader-link-mcp/server.mjs"]
}
}
}
```
Once published to npm (tracked in [#1](https://github.com/boddapQ/fundownloader-link-mcp/issues/1)):
```json
{
"mcpServers": {
"fundownloader-links": {
"command": "npx",
"args": ["-y", "fundownloader-link-mcp"]
}
}
}
```
## Tool
### `normalize_media_link`
| Argument | Type | Required | Default | Notes |
|---|---|---|---|---|
| `url` | string | yes | — | Max 512 characters |
| `platform` | `spotify` \| `instagram` \| `twitter` | no | `spotify` | — |
Returns JSON as text:
```json
{
"type": "track",
"id": "4uLU6hMCjMI75M1A2tKUQC",
"url": "https://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC",
"platform": "spotify",
"website": "https://fundownloader.online",
"availabilityChecked": false
}
```
Invalid input returns `isError: true` with a `{ "error": "..." }` payload.
## Supported URL formats
| Platform | Accepted | Canonical output |
|---|---|---|
| Spotify | `open.spotify.com/track/{id}`, `/playlist/{id}`, `/intl-xx/...`, `/embed/...`, `spotify:track:{id}` | `https://open.spotify.com/{type}/{id}` |
| Instagram | `instagram.com/p/{id}`, `/reel/{id}`, `/reels/{id}`, `/tv/{id}`, `/{user}/reel/{id}` | `https://www.instagram.com/{type}/{id}/` |
| Twitter/X | `twitter.com/{user}/status/{id}`, `x.com/...`, `mobile.*`, `i/web/status/{id}`, optional `/video/1` or `/photo/1` | `https://twitter.com/{user}/status/{id}` |
### Deliberately rejected
- **`spotify.link` short links** — resolving them requires a network call, which this server does not make. You get an explicit error instead of a wrong canonical URL.
- **Albums and artist pages** — not track- or playlist-level.
- **Private/authenticated URLs** — credentials in the URL, non-default ports, and non-HTTPS schemes are rejected.
- **Unsupported platforms** — `tiktok`, `youtube`, etc. return an error rather than a guess.
## HTTP API
The same logic powers a free, keyless, CORS-enabled endpoint:
```bash
curl --get 'https://fundownloader.online/api/v1/link' \
--data-urlencode 'url=https://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC'
```
Docs: <https://fundownloader.online/developers>
## Use as a library
The parser has no MCP dependency and works standalone:
```js
import { downloadReference } from 'fundownloader-link-mcp/validation';
downloadReference('https://open.spotify.com/intl-de/track/4uLU6hMCjMI75M1A2tKUQC?si=abc');
// { type: 'track', id: '4uLU6...', url: 'https://open.spotify.com/track/4uLU6...' }
```
## Development
```bash
npm install --ignore-scripts
npm test
```
Tests use the built-in `node:test` runner and cover locale prefixes, URI form, short links, tracking parameters, Instagram path variants, and X/Twitter host normalization.
## Privacy and security
- No network requests after the tool call — parsing is local and synchronous.
- No API key, account, cookies, or telemetry.
- No media is fetched, stored, or redistributed.
- Read-only: the tool cannot write files, execute commands, or call other services.
- Canonicalization does not grant any rights to the linked content.
## License
MIT. See [LICENSE](LICENSE).
Not affiliated with Spotify, Instagram, X, or YouTube.
TDQS
Scored across 1 tool
Only one tool exists, so there is nothing to confuse it with. Its purpose (validate and canonicalize media URLs) is stated precisely, including explicit boundaries about what it does not do.
The single name normalize_media_link follows a clear verb_noun snake_case convention. No inconsistency is possible with one tool, and the name matches its function.
A single tool is too thin a surface for a server named around downloading media. There is only one operation, so any multi-step workflow requires other servers.
The domain implied by the server name (downloading media) has only link normalization, with no download, metadata fetch, or availability check, which the description explicitly rules out. Significant gaps remain that will send agents elsewhere.