The MCP server plugs CustomSongwriter straight into AI agents. Claude, Cursor or an agent of your own can have a song written and produced inside a conversation, without anyone hand-rolling a REST call.
Last updated: 2026-09-16
Keys are issued by hand. A short mail with your use case, expected volume and languages is enough, and access usually takes one business day.
The Model Context Protocol is an open standard for how an AI agent reaches external tools and data. Instead of writing an API client, you add the server once to the agent’s configuration. From then on the model knows which tools exist and calls them itself when the conversation calls for it.
Our server exposes the same capabilities as the REST API: have a song written and produced, check its state, fetch the lyrics, regenerate, create a payment link. The difference is the framing. The tool descriptions are written so a model understands when a song makes sense at all and which details it should collect first.
The address is https://mcp.customsongwriter.com/mcp. It is the same service as at /api/ in a different wrapper, and it uses the same keys. Run both side by side and you see the same songs in both worlds.
Here too we bill only per finished song, currently $29.99. Tool calls that only read or prepare something cost nothing.
The route is the same as for the REST API: keys are issued by hand. Write to songs@maxkuch.com and say briefly which agent you want to connect, what it should do and what volume to expect.
You get a test key (sk_test_) and a live key (sk_live_). If you already have an API key you do not need a second one: the same key opens the MCP server.
For teams that want to hand the server to several people we issue several keys on one account on request. That way you can see afterwards which key produced which song.
The server speaks MCP over HTTP with server-sent events for the return channel, which is the transport current clients use by default. No local process is needed, there is nothing to install.
Authentication uses the same Authorization header as the REST API: Authorization: Bearer sk_live_.... Clients that only speak stdio reach the server through mcp-remote as a bridge, see the Claude Desktop configuration below.
Client and server negotiate the protocol version on connect. We support the current revision and the one before it, so a client update never causes a hard break.
curl https://mcp.customsongwriter.com/health
In Claude Code a single command is enough. The key should come from an environment variable rather than the clipboard.
claude mcp add --transport http songs \
https://mcp.customsongwriter.com/mcp \
--header "Authorization: Bearer $SONG_API_KEY"
Claude Desktop reads its server list from claude_desktop_config.json. The entry bridges to the HTTP transport through mcp-remote.
{
"mcpServers": {
"songs": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.customsongwriter.com/mcp",
"--header", "Authorization: Bearer ${SONG_API_KEY}"],
"env": { "SONG_API_KEY": "sk_live_..." }
}
}
}
Cursor, Windsurf, Zed and most other clients take the server URL directly and allow custom headers, which removes the need for a bridge.
{
"mcpServers": {
"songs": {
"url": "https://mcp.customsongwriter.com/mcp",
"headers": { "Authorization": "Bearer sk_live_..." }
}
}
}
After adding it the client should show seven tools. If the list stays empty it is almost always the header: an expired or mistyped key produces an empty tool list rather than a visible error.
The server exposes seven tools. Writing tools are marked as such so clients can ask for confirmation where they want to.
| Tool | Kind | Description |
|---|---|---|
| create_song | Writes and produces a song. Returns a song id immediately, the recording follows a few minutes later. On request the call blocks until the preview is ready. | |
| get_song | Returns the current state of a song including status, preview link and payment state. | |
| list_songs | Lists recently created songs, optionally filtered by status. Useful when the agent picks the thread back up after a break. | |
| get_lyrics | Returns the full lyrics as text. The lyrics are always free, even before payment. | |
| regenerate_song | Creates a new version, either with the same lyrics and a new recording or from scratch. Up to three times per song, at no extra cost. | |
| get_checkout_link | Creates a payment link for a song and returns it as a URL so the agent can pass it on in the conversation. | |
| list_options | Names the valid values for occasion, mood, voice and language. Agents should call this once per session instead of guessing values. |
Reading tools are idempotent and may be called without asking. create_song and regenerate_song change state and cause production cost, so they announce themselves to the client as writing tools.
The tool that matters is create_song. Its schema is deliberately narrow: three required fields, the rest optional with sensible defaults. The fewer decisions a model has to make, the less often it invents values.
{
"name": "create_song",
"description": "Write and produce a personalised song. Returns immediately with a song id; the recording is ready a few minutes later.",
"inputSchema": {
"type": "object",
"required": ["occasion", "recipient_name", "details"],
"properties": {
"occasion": { "type": "string", "enum": ["birthday", "wedding", "anniversary", "farewell", "funeral", "christening", "graduation", "christmas", "declaration", "other"] },
"recipient_name": { "type": "string", "maxLength": 80 },
"relationship": { "type": "string", "maxLength": 80 },
"language": { "type": "string", "default": "en" },
"mood": { "type": "string", "enum": ["happy", "warm", "funny", "romantic", "gentle", "epic", "surprise_me"] },
"style": { "type": "string" },
"voice": { "type": "string", "enum": ["female", "male", "duet", "choir", "childrens", "surprise_me"] },
"details": { "type": "string", "minLength": 40, "maxLength": 4000 },
"wait": { "type": "boolean", "default": false, "description": "Block until the preview is ready, at most 10 minutes." }
}
}
}
The lever is details. Concrete things belong there: nicknames, shared experiences, quirks, running jokes. A song built from three adjectives sounds like three adjectives. Forty characters are the minimum, and the tool description explicitly tells the model to ask rather than invent details.
The wait field controls how the waiting is handled. The default is false: the call returns at once, the agent can keep talking and call get_song later. With true the call blocks until the 45 second preview exists, for at most ten minutes. For conversational agents false is almost always the better choice.
Without language the song is sung in en, the language of this domain. All common European languages and Japanese are possible, and list_options returns the valid list.
Tools answer on two tracks: a text block for the model and, where it helps, structuredContent for the client. The text block is phrased so the model can read it out to the user without translating it first.
{
"content": [
{ "type": "text", "text": "Song sng_3n8Kd2ZpQv for Anna is written. Lyrics below, the 45 second preview is ready." },
{ "type": "resource", "resource": { "uri": "song://sng_3n8Kd2ZpQv/lyrics", "mimeType": "text/plain" } },
{ "type": "resource", "resource": { "uri": "song://sng_3n8Kd2ZpQv/preview", "mimeType": "audio/mpeg" } }
],
"structuredContent": {
"id": "sng_3n8Kd2ZpQv",
"status": "preview_ready",
"preview_url": "https://cdn.customsongwriter.com/preview/sng_3n8Kd2ZpQv.mp3",
"paid": false
},
"isError": false
}
Lyrics and audio come back as resource references, not as embedded data. A client that can play audio resolves the reference. A client that cannot ignores it and keeps the text. That way an MP3 never ends up written into the context window.
Paid songs additionally carry audio_url in structuredContent with the full recording. The link is valid for 24 hours and can be refreshed at any time through get_song.
Besides tools the server exposes resources under the song:// scheme. Clients that support resources can display them or attach them to the model without calling a tool.
song://sng_3n8Kd2ZpQv the song object as JSON
song://sng_3n8Kd2ZpQv/lyrics the full lyrics as plain text
song://sng_3n8Kd2ZpQv/preview the first 45 seconds as audio/mpeg
song://sng_3n8Kd2ZpQv/audio the full recording, only after payment
The list of available resources changes during a session as new songs appear. The server sends a change notification in that case so clients can refresh their list.
The audio resource exists only after payment. Reaching for it earlier is not an error in the technical sense but returns a note with the payment link.
For the most common occasions there are ready-made prompts. They collect what a good song needs and then call create_song. In clients that support prompts they appear as quick commands.
{
"name": "birthday_song",
"description": "Collects the five things a birthday song needs and then creates it.",
"arguments": [
{ "name": "recipient_name", "required": true },
{ "name": "age", "required": false },
{ "name": "details", "required": false }
]
}
Currently available are birthday_song, wedding_song, farewell_song and christmas_song. If you prefer your own flow, ignore the prompts and call the tools directly.
This is what it looks like in practice. The agent collects details, calls the tool, hands over lyrics and preview and takes corrections.
User: My sister Anna turns 34 on Friday. She climbs, she is always late,
and she calls everyone chef. Make her a song.
Claude: [calls create_song with occasion=birthday, recipient_name=Anna,
relationship=sister, mood=funny, details="climbs every weekend,
always ten minutes late, calls everyone chef"]
Done. Here are the lyrics, and the first 45 seconds are playable
right away. Should the chorus lean more on the climbing or more
on the chef thing?
User: More chef.
Claude: [calls regenerate_song with keep_lyrics=false,
note="put the chef running gag in the chorus"]
New version is running, about five minutes.
The second step is the interesting one: a regeneration costs nothing and takes a few minutes again. That is exactly what the preview is for, and agents should offer it actively instead of treating the first version as final.
An agent cannot trigger a payment. It can only create a payment link and pass it on, and paying happens in the browser. That is deliberate: a model should not make a purchase decision a human has not seen.
get_checkout_link returns a URL that is valid for 24 hours. After payment the song moves to complete and the next get_song carries the full recording. The agent does not have to subscribe to anything, a later call is enough.
If you handle payment in your own system and only settle with us, we enable the direct route from the REST API on request. The payment link then disappears and the song is released immediately.
Every key carries permissions. The default is read and write without billing access, which suits most agents.
| Permission | Identifier | Meaning |
|---|---|---|
| songs:read | Retrieve songs, list them, read lyrics. Without this permission the server reports an empty tool list. | |
| songs:write | Create and regenerate songs. Causes production cost. | |
| billing | Create payment links and read payment state. Only needed if the agent should hand links on. |
Tools the key is not permitted to use do not appear in the tool list at all. That is kinder than an error mid-conversation, because the model then never offers something it cannot do anyway.
Errors come back as a normal tool result with isError: true, not as a protocol error. The text is addressed to the model and says what to do, so the agent can react sensibly inside the conversation.
{
"content": [
{ "type": "text", "text": "The song is not paid for yet, so the full recording cannot be handed over. The checkout link is https://pay.customsongwriter.com/c/cs_live_8Hd2Kq..." }
],
"isError": true
}
Real protocol errors happen only on an invalid key, a missing permission or a malformed request. Everything that can go wrong on the business side, such as missing details, an unpaid song or a limit reached, comes back as text.
Productions that stall move to failed after 15 minutes and cost nothing. The agent can simply call again afterwards.
The same limits apply as in the REST API: 60 tool calls per minute per key, at most ten concurrent productions, up to three regenerations per song. For higher numbers a mail is enough.
Exceeding a limit does not produce a hard error but a text result saying when things resume. Agents should then wait rather than call again immediately.
Songs stay retrievable for 90 days. After that they disappear along with their inputs, and get_song reports them as unknown.
What you send in details goes into producing that one song and nothing else. We do not train our own models on it. A song can be deleted early at any time through DELETE /v1/songs/{id} in the REST API.
You receive a non-exclusive, expressly commercial right to use the finished song. Whether a separate copyright arises in an AI-generated recording is not settled in many jurisdictions. If you depend on that, have it checked first.
If your agent passes on data about your customers, you are the controller and we are the processor. A data processing agreement is available on request.
The server runs on the same infrastructure as the REST API. Maintenance windows are announced by mail to the address on file, and changes to tool schemas are additive only.
When a new tool appears the server sends a change notification. Clients that act on it see the tool without a restart. Existing tools keep their names and their required fields.
Questions, higher limits, your own prompts or tools for a specific flow: songs@maxkuch.com. For technical problems quote the song id and we find the request immediately.
If you would rather work against plain HTTP, the same capabilities are documented as a REST interface at /api/. Both routes share keys, limits and billing.
Keys are issued by hand. A short mail with your use case, expected volume and languages is enough, and access usually takes one business day.