Skip to main content
Glama

Podcast MCP

A lightweight MCP server that turns a podcast script into a finished MP3, designed to run on Render Free. Three CPU-only TTS engines, switched with TTS_ENGINE:

  • piper (Render default) — Piper TTS with rhasspy voices (~63 MB each, GPL-licensed engine). Clear voices and the only engine that fits the free 512MB instance under real load (~230 MB steady, ~370 MB peak, measured).

  • kitten (local default) — KittenTTS Nano. Small download, but onnxruntime peaks at ~550 MB during generation — gets OOM-killed on Render Free.

  • kokoro (opt-in) — Kokoro-82M via ONNX. The most natural voices; needs a paid instance.

The reasoning stays in your main app; this service only does audio:

Report App (its own LLM)
   ↓  report → HOST/GUEST dialogue script
Podcast MCP on Render
   ↓  1. parse script into speaker turns
   ↓  2. split turns into TTS-safe chunks
   ↓  3. KittenTTS generates host + guest audio
   ↓  4. merge with natural pauses → MP3
   ↓  5. serve file at /audio/<name>.mp3
returns audio_url
   ↓
Report App shows audio player

Endpoints

Path

What

POST /mcp

MCP streamable-HTTP endpoint (stateless, JSON responses)

GET /health

Health check (used by Render)

GET /audio/{filename}

Serves generated MP3/WAV files

Related MCP server: mcp-podcast-generator

MCP tools

generate_podcast_from_script

generate_podcast_from_script(
    script: str,            # "HOST: ...\nGUEST: ..." (any speaker labels work)
    title: str = "",
    host_voice: str = "",   # empty = engine default (piper: en_US-hfc_male-medium)
    guest_voice: str = "",  # empty = engine default (piper: en_US-hfc_female-medium)
    speed: float = 1.0,
)

Returns:

{
  "success": true,
  "type": "podcast",
  "title": "Q2 Business Review",
  "audio_url": "https://podcast-mcp.onrender.com/audio/q2-business-review-a1b2c3d4.mp3",
  "duration_seconds": 312.4,
  "turns": 14,
  "voices": {"HOST": "Jasper", "GUEST": "Bella"}
}

Script format (markdown decoration and [cues] are tolerated; unlabeled lines continue the previous speaker):

HOST: Welcome back to the show. Today we're looking at the Q2 results.
GUEST: Thanks for having me. The headline: revenue grew 18 percent.
HOST: Let's break that down...

generate_video_from_sections

Renders a narrated slide video (MP4): one slide per section — title, bullet points, optional PIL-drawn bar chart — shown for the length of its narration. Send the same structured media_content your LLM produces for the podcast to keep both outputs consistent:

{
  "title": "Q2 Portfolio Review",
  "sections": [
    {
      "title": "Performance",
      "narration": "Your portfolio returned 2.41 percent while the benchmark returned 3.77 percent.",
      "key_points": ["Portfolio: 2.41%", "Benchmark: 3.77%"],
      "visual": {"type": "bar_chart", "data": {"Portfolio": 2.41, "Benchmark": 3.77}}
    }
  ]
}

Call with {"sections": [...], "title": "...", "voice": "", "speed": 1.0} → returns {"success": true, "type": "video", "video_url": "...", "duration_seconds": ...}. visual.type supports bar_chart (needs data) and bullet_summary (default). Run it in parallel with the podcast tool from one shared JSON:

podcast, video = await asyncio.gather(
    session.call_tool("generate_podcast_from_script", {"script": script, "title": title}),
    session.call_tool("generate_video_from_sections", {"sections": sections, "title": title}),
)

(Note: on the single-CPU free instance the two run back-to-back internally — parallel calls are safe, just not faster.)

text_to_speech

text_to_speech(text: str, voice: str = "", speed: float = 1.0, format: str = "mp3")

list_voices

Returns the active engine, its voices, and the current defaults.

  • piper: en_US-hfc_male-medium (default host), en_US-hfc_female-medium (default guest), plus ryan/amy/lessac/joe/kristin/kusal and en_GB voices; any voice name from rhasspy/piper-voices works and is downloaded on demand.

Languages (piper engine)

Piper voices are natively trained per language (~35 languages in the rhasspy catalog) — this is real multilingual speech, not phonemization tricks. Write the script in the target language and pick voices whose locale matches:

Language

Example host / guest voices

English

en_US-hfc_male-medium / en_US-hfc_female-medium

Dutch

nl_NL-mls-medium / nl_BE-nathalie-medium

German

de_DE-thorsten-medium

French / Spanish

fr_FR-siwis-medium / es_ES-davefx-medium

Hindi

hi_IN-rohan-medium / hi_IN-priyamvada-medium

Telugu

te_IN-venkatesh-medium / te_IN-maya-medium

A voice speaks only its own language — don't mix an en_US voice with a Dutch script. Note: the kitten engine is English-only (espeak can phonemize 100+ languages, but the acoustic model is trained on English, so other languages come out garbled — don't advertise it as multilingual).

  • kokoro: 27 English voices — af_*/am_* American female/male, bf_*/bm_* British (e.g. af_heart, af_bella, am_michael, am_adam, bf_emma, bm_george).

  • kitten: Bella, Jasper, Luna, Bruno, Rosie, Hugo, Kiki, Leo.

Calling it from your report app

With the official Python MCP client:

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async def make_podcast(script: str, title: str) -> str:
    async with streamablehttp_client("https://podcast-mcp.onrender.com/mcp") as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "generate_podcast_from_script",
                {"script": script, "title": title},
            )
            return result.structuredContent["audio_url"]

Or add it to any MCP-capable agent as a remote server with URL https://<your-service>.onrender.com/mcp.

Deploy on Render (free)

  1. Push this repo to GitHub.

  2. In Render: New → Blueprint, pick the repo — render.yaml provisions a free Docker web service with /health checks.

  3. Done. RENDER_EXTERNAL_URL is used automatically to build audio_urls (override with PUBLIC_BASE_URL if you put a domain in front).

Notes for the free tier:

  • First boot downloads the two default piper voices (~126 MB) into /tmp in a background preload, so the service is healthy immediately; the first tool call may wait on it.

  • The instance sleeps after idle; the first request after a sleep takes ~1 min plus the model re-download (the disk is wiped on sleep/restart).

  • Keep TTS_ENGINE=piper on the free instance — kitten and kokoro exceed 512 MB under load and get OOM-killed (502s mid-generation). They work on paid instances.

  • Audio files live on ephemeral disk and are deleted after AUDIO_TTL_HOURS (24h default) or on restart — have your app fetch/cache the MP3 promptly if it must keep it.

Configuration (env vars)

Var

Default

Purpose

TTS_ENGINE

kitten (render.yaml sets piper)

piper (free-tier safe), kitten, or kokoro (needs paid RAM)

LOW_MEMORY_MODE

1

Disables onnxruntime's memory arena + caps threads. Set 0 on big instances for speed

TTS_PACE

0.6

Duty-cycle pacing: per-chunk sleep = chunk time × this, so health checks stay alive on 0.1-CPU instances. Set 0 on real CPUs

PIPER_MODEL_DIR

models/piper (/tmp/piper in Docker)

Where Piper voice files are cached

KOKORO_VARIANT

int8

int8 (114 MB), fp16 (164 MB), or fp32 (326 MB)

KOKORO_MODEL_DIR

models/kokoro (/tmp/kokoro in Docker)

Where Kokoro model files are cached

KITTEN_MODEL

KittenML/kitten-tts-nano-0.8

Full-precision nano (~56MB). The -int8 variant is smaller but has known quality issues

DEFAULT_HOST_VOICE / DEFAULT_GUEST_VOICE

engine defaults

Override default voices

PUBLIC_BASE_URL

RENDER_EXTERNAL_URL or localhost

Base for returned audio_url

AUDIO_DIR

audio_output (/tmp/podcast_audio in Docker)

Where files are written

AUDIO_TTL_HOURS

24

Delete generated files older than this

MAX_SCRIPT_CHARS

20000

Reject oversized scripts

PRELOAD_MODEL

1

Load the model in the background at boot

Run locally

Works with plain pip on Windows/Mac/Linux (KittenTTS 0.8.1 bundles espeak via espeakng-loader, no system packages needed):

pip install -r requirements.txt
python server.py

Then the MCP endpoint is http://localhost:8000/mcp. Or with Docker (same image Render uses):

docker build -t podcast-mcp .
docker run -p 8000:8000 podcast-mcp

Pure-logic tests (no model needed):

python test_logic.py

Related MCP Connectors

Related MCP Servers